1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.TestApi;
23 import android.content.Context;
24 import android.hardware.cas.AidlCasPluginDescriptor;
25 import android.hardware.cas.ICas;
26 import android.hardware.cas.ICasListener;
27 import android.hardware.cas.IMediaCasService;
28 import android.hardware.cas.Status;
29 import android.hardware.cas.V1_0.HidlCasPluginDescriptor;
30 import android.media.MediaCasException.*;
31 import android.media.tv.TvInputService.PriorityHintUseCaseType;
32 import android.media.tv.tunerresourcemanager.CasSessionRequest;
33 import android.media.tv.tunerresourcemanager.ResourceClientProfile;
34 import android.media.tv.tunerresourcemanager.TunerResourceManager;
35 import android.os.Bundle;
36 import android.os.Handler;
37 import android.os.HandlerThread;
38 import android.os.IHwBinder;
39 import android.os.Looper;
40 import android.os.Message;
41 import android.os.Process;
42 import android.os.RemoteException;
43 import android.os.ServiceManager;
44 import android.os.ServiceSpecificException;
45 import android.util.Log;
46 import android.util.Singleton;
47 
48 import com.android.internal.util.FrameworkStatsLog;
49 
50 import java.lang.annotation.Retention;
51 import java.lang.annotation.RetentionPolicy;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.HashMap;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Objects;
58 
59 /**
60  * MediaCas can be used to obtain keys for descrambling protected media streams, in
61  * conjunction with {@link android.media.MediaDescrambler}. The MediaCas APIs are
62  * designed to support conditional access such as those in the ISO/IEC13818-1.
63  * The CA system is identified by a 16-bit integer CA_system_id. The scrambling
64  * algorithms are usually proprietary and implemented by vendor-specific CA plugins
65  * installed on the device.
66  * <p>
67  * The app is responsible for constructing a MediaCas object for the CA system it
68  * intends to use. The app can query if a certain CA system is supported using static
69  * method {@link #isSystemIdSupported}. It can also obtain the entire list of supported
70  * CA systems using static method {@link #enumeratePlugins}.
71  * <p>
72  * Once the MediaCas object is constructed, the app should properly provision it by
73  * using method {@link #provision} and/or {@link #processEmm}. The EMMs (Entitlement
74  * management messages) can be distributed out-of-band, or in-band with the stream.
75  * <p>
76  * To descramble elementary streams, the app first calls {@link #openSession} to
77  * generate a {@link Session} object that will uniquely identify a session. A session
78  * provides a context for subsequent key updates and descrambling activities. The ECMs
79  * (Entitlement control messages) are sent to the session via method
80  * {@link Session#processEcm}.
81  * <p>
82  * The app next constructs a MediaDescrambler object, and initializes it with the
83  * session using {@link MediaDescrambler#setMediaCasSession}. This ties the
84  * descrambler to the session, and the descrambler can then be used to descramble
85  * content secured with the session's key, either during extraction, or during decoding
86  * with {@link android.media.MediaCodec}.
87  * <p>
88  * If the app handles sample extraction using its own extractor, it can use
89  * MediaDescrambler to descramble samples into clear buffers (if the session's license
90  * doesn't require secure decoders), or descramble a small amount of data to retrieve
91  * information necessary for the downstream pipeline to process the sample (if the
92  * session's license requires secure decoders).
93  * <p>
94  * If the session requires a secure decoder, a MediaDescrambler needs to be provided to
95  * MediaCodec to descramble samples queued by {@link MediaCodec#queueSecureInputBuffer}
96  * into protected buffers. The app should use {@link MediaCodec#configure(MediaFormat,
97  * android.view.Surface, int, MediaDescrambler)} instead of the normal {@link
98  * MediaCodec#configure(MediaFormat, android.view.Surface, MediaCrypto, int)} method
99  * to configure MediaCodec.
100  * <p>
101  * <h3>Using Android's MediaExtractor</h3>
102  * <p>
103  * If the app uses {@link MediaExtractor}, it can delegate the CAS session
104  * management to MediaExtractor by calling {@link MediaExtractor#setMediaCas}.
105  * MediaExtractor will take over and call {@link #openSession}, {@link #processEmm}
106  * and/or {@link Session#processEcm}, etc.. if necessary.
107  * <p>
108  * When using {@link MediaExtractor}, the app would still need a MediaDescrambler
109  * to use with {@link MediaCodec} if the licensing requires a secure decoder. The
110  * session associated with the descrambler of a track can be retrieved by calling
111  * {@link MediaExtractor#getCasInfo}, and used to initialize a MediaDescrambler
112  * object for MediaCodec.
113  * <p>
114  * <h3>Listeners</h3>
115  * <p>The app may register a listener to receive events from the CA system using
116  * method {@link #setEventListener}. The exact format of the event is scheme-specific
117  * and is not specified by this API.
118  */
119 public final class MediaCas implements AutoCloseable {
120     private static final String TAG = "MediaCas";
121     private ICas mICas = null;
122     private android.hardware.cas.V1_0.ICas mICasHidl = null;
123     private android.hardware.cas.V1_1.ICas mICasHidl11 = null;
124     private android.hardware.cas.V1_2.ICas mICasHidl12 = null;
125     private EventListener mListener;
126     private HandlerThread mHandlerThread;
127     private EventHandler mEventHandler;
128     private @PriorityHintUseCaseType int mPriorityHint;
129     private String mTvInputServiceSessionId;
130     private int mClientId;
131     private int mCasSystemId;
132     private int mUserId;
133     private TunerResourceManager mTunerResourceManager = null;
134     private final Map<Session, Integer> mSessionMap = new HashMap<>();
135 
136     /**
137      * Scrambling modes used to open cas sessions.
138      *
139      * @hide
140      */
141     @IntDef(
142             prefix = "SCRAMBLING_MODE_",
143             value = {
144                 SCRAMBLING_MODE_RESERVED,
145                 SCRAMBLING_MODE_DVB_CSA1,
146                 SCRAMBLING_MODE_DVB_CSA2,
147                 SCRAMBLING_MODE_DVB_CSA3_STANDARD,
148                 SCRAMBLING_MODE_DVB_CSA3_MINIMAL,
149                 SCRAMBLING_MODE_DVB_CSA3_ENHANCE,
150                 SCRAMBLING_MODE_DVB_CISSA_V1,
151                 SCRAMBLING_MODE_DVB_IDSA,
152                 SCRAMBLING_MODE_MULTI2,
153                 SCRAMBLING_MODE_AES128,
154                 SCRAMBLING_MODE_AES_CBC,
155                 SCRAMBLING_MODE_AES_ECB,
156                 SCRAMBLING_MODE_AES_SCTE52,
157                 SCRAMBLING_MODE_TDES_ECB,
158                 SCRAMBLING_MODE_TDES_SCTE52
159             })
160     @Retention(RetentionPolicy.SOURCE)
161     public @interface ScramblingMode {}
162 
163     /** DVB (Digital Video Broadcasting) reserved mode. */
164     public static final int SCRAMBLING_MODE_RESERVED = android.hardware.cas.ScramblingMode.RESERVED;
165 
166     /** DVB (Digital Video Broadcasting) Common Scrambling Algorithm (CSA) 1. */
167     public static final int SCRAMBLING_MODE_DVB_CSA1 = android.hardware.cas.ScramblingMode.DVB_CSA1;
168 
169     /** DVB CSA 2. */
170     public static final int SCRAMBLING_MODE_DVB_CSA2 = android.hardware.cas.ScramblingMode.DVB_CSA2;
171 
172     /** DVB CSA 3 in standard mode. */
173     public static final int SCRAMBLING_MODE_DVB_CSA3_STANDARD =
174             android.hardware.cas.ScramblingMode.DVB_CSA3_STANDARD;
175 
176     /** DVB CSA 3 in minimally enhanced mode. */
177     public static final int SCRAMBLING_MODE_DVB_CSA3_MINIMAL =
178             android.hardware.cas.ScramblingMode.DVB_CSA3_MINIMAL;
179 
180     /** DVB CSA 3 in fully enhanced mode. */
181     public static final int SCRAMBLING_MODE_DVB_CSA3_ENHANCE =
182             android.hardware.cas.ScramblingMode.DVB_CSA3_ENHANCE;
183 
184     /** DVB Common IPTV Software-oriented Scrambling Algorithm (CISSA) Version 1. */
185     public static final int SCRAMBLING_MODE_DVB_CISSA_V1 =
186             android.hardware.cas.ScramblingMode.DVB_CISSA_V1;
187 
188     /** ATIS-0800006 IIF Default Scrambling Algorithm (IDSA). */
189     public static final int SCRAMBLING_MODE_DVB_IDSA = android.hardware.cas.ScramblingMode.DVB_IDSA;
190 
191     /** A symmetric key algorithm. */
192     public static final int SCRAMBLING_MODE_MULTI2 = android.hardware.cas.ScramblingMode.MULTI2;
193 
194     /** Advanced Encryption System (AES) 128-bit Encryption mode. */
195     public static final int SCRAMBLING_MODE_AES128 = android.hardware.cas.ScramblingMode.AES128;
196 
197     /** Advanced Encryption System (AES) Cipher Block Chaining (CBC) mode. */
198     public static final int SCRAMBLING_MODE_AES_CBC = android.hardware.cas.ScramblingMode.AES_CBC;
199 
200     /** Advanced Encryption System (AES) Electronic Code Book (ECB) mode. */
201     public static final int SCRAMBLING_MODE_AES_ECB = android.hardware.cas.ScramblingMode.AES_ECB;
202 
203     /**
204      * Advanced Encryption System (AES) Society of Cable Telecommunications Engineers (SCTE) 52
205      * mode.
206      */
207     public static final int SCRAMBLING_MODE_AES_SCTE52 =
208             android.hardware.cas.ScramblingMode.AES_SCTE52;
209 
210     /** Triple Data Encryption Algorithm (TDES) Electronic Code Book (ECB) mode. */
211     public static final int SCRAMBLING_MODE_TDES_ECB = android.hardware.cas.ScramblingMode.TDES_ECB;
212 
213     /**
214      * Triple Data Encryption Algorithm (TDES) Society of Cable Telecommunications Engineers (SCTE)
215      * 52 mode.
216      */
217     public static final int SCRAMBLING_MODE_TDES_SCTE52 =
218             android.hardware.cas.ScramblingMode.TDES_SCTE52;
219 
220     /**
221      * Usages used to open cas sessions.
222      *
223      * @hide
224      */
225     @IntDef(prefix = "SESSION_USAGE_",
226             value = {SESSION_USAGE_LIVE, SESSION_USAGE_PLAYBACK, SESSION_USAGE_RECORD,
227             SESSION_USAGE_TIMESHIFT})
228     @Retention(RetentionPolicy.SOURCE)
229     public @interface SessionUsage {}
230 
231     /** Cas session is used to descramble live streams. */
232     public static final int SESSION_USAGE_LIVE = android.hardware.cas.SessionIntent.LIVE;
233 
234     /** Cas session is used to descramble recoreded streams. */
235     public static final int SESSION_USAGE_PLAYBACK = android.hardware.cas.SessionIntent.PLAYBACK;
236 
237     /** Cas session is used to descramble live streams and encrypt local recorded content */
238     public static final int SESSION_USAGE_RECORD = android.hardware.cas.SessionIntent.RECORD;
239 
240     /**
241      * Cas session is used to descramble live streams , encrypt local recorded content and playback
242      * local encrypted content.
243      */
244     public static final int SESSION_USAGE_TIMESHIFT = android.hardware.cas.SessionIntent.TIMESHIFT;
245 
246     /**
247      * Plugin status events sent from cas system.
248      *
249      * @hide
250      */
251     @IntDef(prefix = "PLUGIN_STATUS_",
252             value = {PLUGIN_STATUS_PHYSICAL_MODULE_CHANGED, PLUGIN_STATUS_SESSION_NUMBER_CHANGED})
253     @Retention(RetentionPolicy.SOURCE)
254     public @interface PluginStatus {}
255 
256     /**
257      * The event to indicate that the status of CAS system is changed by the removal or insertion of
258      * physical CAS modules.
259      */
260     public static final int PLUGIN_STATUS_PHYSICAL_MODULE_CHANGED =
261             android.hardware.cas.StatusEvent.PLUGIN_PHYSICAL_MODULE_CHANGED;
262 
263     /** The event to indicate that the number of CAS system's session is changed. */
264     public static final int PLUGIN_STATUS_SESSION_NUMBER_CHANGED =
265             android.hardware.cas.StatusEvent.PLUGIN_SESSION_NUMBER_CHANGED;
266 
267     private static final Singleton<IMediaCasService> sService =
268             new Singleton<IMediaCasService>() {
269                 @Override
270                 protected IMediaCasService create() {
271                     try {
272                         Log.d(TAG, "Trying to get AIDL service");
273                         IMediaCasService serviceAidl =
274                                 IMediaCasService.Stub.asInterface(
275                                         ServiceManager.getService(
276                                                 IMediaCasService.DESCRIPTOR + "/default"));
277                         if (serviceAidl != null) {
278                             return serviceAidl;
279                         }
280                     } catch (Exception eAidl) {
281                         Log.d(TAG, "Failed to get cas AIDL service");
282                     }
283                     return null;
284                 }
285             };
286 
287     private static final Singleton<android.hardware.cas.V1_0.IMediaCasService> sServiceHidl =
288             new Singleton<android.hardware.cas.V1_0.IMediaCasService>() {
289                 @Override
290                 protected android.hardware.cas.V1_0.IMediaCasService create() {
291                     try {
292                         Log.d(TAG, "Trying to get cas@1.2 service");
293                         android.hardware.cas.V1_2.IMediaCasService serviceV12 =
294                                 android.hardware.cas.V1_2.IMediaCasService.getService(
295                                         true /*wait*/);
296                         if (serviceV12 != null) {
297                             return serviceV12;
298                         }
299                     } catch (Exception eV1_2) {
300                         Log.d(TAG, "Failed to get cas@1.2 service");
301                     }
302 
303                     try {
304                         Log.d(TAG, "Trying to get cas@1.1 service");
305                         android.hardware.cas.V1_1.IMediaCasService serviceV11 =
306                                 android.hardware.cas.V1_1.IMediaCasService.getService(
307                                         true /*wait*/);
308                         if (serviceV11 != null) {
309                             return serviceV11;
310                         }
311                     } catch (Exception eV1_1) {
312                         Log.d(TAG, "Failed to get cas@1.1 service");
313                     }
314 
315                     try {
316                         Log.d(TAG, "Trying to get cas@1.0 service");
317                         return android.hardware.cas.V1_0.IMediaCasService.getService(true /*wait*/);
318                     } catch (Exception eV1_0) {
319                         Log.d(TAG, "Failed to get cas@1.0 service");
320                     }
321 
322                     return null;
323                 }
324             };
325 
getService()326     static IMediaCasService getService() {
327         return sService.get();
328     }
329 
getServiceHidl()330     static android.hardware.cas.V1_0.IMediaCasService getServiceHidl() {
331         return sServiceHidl.get();
332     }
333 
validateInternalStates()334     private void validateInternalStates() {
335         if (mICas == null && mICasHidl == null) {
336             throw new IllegalStateException();
337         }
338     }
339 
cleanupAndRethrowIllegalState()340     private void cleanupAndRethrowIllegalState() {
341         mICas = null;
342         mICasHidl = null;
343         mICasHidl11 = null;
344         mICasHidl12 = null;
345         throw new IllegalStateException();
346     }
347 
348     private class EventHandler extends Handler {
349 
350         private static final int MSG_CAS_EVENT = 0;
351         private static final int MSG_CAS_SESSION_EVENT = 1;
352         private static final int MSG_CAS_STATUS_EVENT = 2;
353         private static final int MSG_CAS_RESOURCE_LOST = 3;
354         private static final String SESSION_KEY = "sessionId";
355         private static final String DATA_KEY = "data";
356 
EventHandler(Looper looper)357         public EventHandler(Looper looper) {
358             super(looper);
359         }
360 
361         @Override
handleMessage(Message msg)362         public void handleMessage(Message msg) {
363             if (msg.what == MSG_CAS_EVENT) {
364                 byte[] data = (msg.obj == null) ? new byte[0] : (byte[]) msg.obj;
365                 mListener.onEvent(MediaCas.this, msg.arg1, msg.arg2, data);
366             } else if (msg.what == MSG_CAS_SESSION_EVENT) {
367                 Bundle bundle = msg.getData();
368                 byte[] sessionId = bundle.getByteArray(SESSION_KEY);
369                 byte[] data = bundle.getByteArray(DATA_KEY);
370                 mListener.onSessionEvent(
371                         MediaCas.this, createFromSessionId(sessionId), msg.arg1, msg.arg2, data);
372             } else if (msg.what == MSG_CAS_STATUS_EVENT) {
373                 if ((msg.arg1 == PLUGIN_STATUS_SESSION_NUMBER_CHANGED)
374                         && (mTunerResourceManager != null)) {
375                     mTunerResourceManager.updateCasInfo(mCasSystemId, msg.arg2);
376                 }
377                 mListener.onPluginStatusUpdate(MediaCas.this, msg.arg1, msg.arg2);
378             } else if (msg.what == MSG_CAS_RESOURCE_LOST) {
379                 mListener.onResourceLost(MediaCas.this);
380             }
381         }
382     }
383 
384     private final ICasListener.Stub mBinder =
385             new ICasListener.Stub() {
386                 @Override
387                 public void onEvent(int event, int arg, byte[] data) throws RemoteException {
388                     if (mEventHandler != null) {
389                         mEventHandler.sendMessage(
390                                 mEventHandler.obtainMessage(
391                                         EventHandler.MSG_CAS_EVENT, event, arg, data));
392                     }
393                 }
394 
395                 @Override
396                 public void onSessionEvent(byte[] sessionId, int event, int arg, byte[] data)
397                         throws RemoteException {
398                     if (mEventHandler != null) {
399                         Message msg = mEventHandler.obtainMessage();
400                         msg.what = EventHandler.MSG_CAS_SESSION_EVENT;
401                         msg.arg1 = event;
402                         msg.arg2 = arg;
403                         Bundle bundle = new Bundle();
404                         bundle.putByteArray(EventHandler.SESSION_KEY, sessionId);
405                         bundle.putByteArray(EventHandler.DATA_KEY, data);
406                         msg.setData(bundle);
407                         mEventHandler.sendMessage(msg);
408                     }
409                 }
410 
411                 @Override
412                 public void onStatusUpdate(byte status, int arg) throws RemoteException {
413                     if (mEventHandler != null) {
414                         mEventHandler.sendMessage(
415                                 mEventHandler.obtainMessage(
416                                         EventHandler.MSG_CAS_STATUS_EVENT, status, arg));
417                     }
418                 }
419 
420                 @Override
421                 public synchronized String getInterfaceHash() throws android.os.RemoteException {
422                     return ICasListener.Stub.HASH;
423                 }
424 
425                 @Override
426                 public int getInterfaceVersion() throws android.os.RemoteException {
427                     return ICasListener.Stub.VERSION;
428                 }
429             };
430 
431     private final android.hardware.cas.V1_2.ICasListener.Stub mBinderHidl =
432             new android.hardware.cas.V1_2.ICasListener.Stub() {
433                 @Override
434                 public void onEvent(int event, int arg, @Nullable ArrayList<Byte> data)
435                         throws RemoteException {
436                     if (mEventHandler != null) {
437                         mEventHandler.sendMessage(
438                                 mEventHandler.obtainMessage(
439                                         EventHandler.MSG_CAS_EVENT, event, arg, toBytes(data)));
440                     }
441                 }
442 
443                 @Override
444                 public void onSessionEvent(
445                         @NonNull ArrayList<Byte> sessionId,
446                         int event,
447                         int arg,
448                         @Nullable ArrayList<Byte> data)
449                         throws RemoteException {
450                     if (mEventHandler != null) {
451                         Message msg = mEventHandler.obtainMessage();
452                         msg.what = EventHandler.MSG_CAS_SESSION_EVENT;
453                         msg.arg1 = event;
454                         msg.arg2 = arg;
455                         Bundle bundle = new Bundle();
456                         bundle.putByteArray(EventHandler.SESSION_KEY, toBytes(sessionId));
457                         bundle.putByteArray(EventHandler.DATA_KEY, toBytes(data));
458                         msg.setData(bundle);
459                         mEventHandler.sendMessage(msg);
460                     }
461                 }
462 
463                 @Override
464                 public void onStatusUpdate(byte status, int arg) throws RemoteException {
465                     if (mEventHandler != null) {
466                         mEventHandler.sendMessage(
467                                 mEventHandler.obtainMessage(
468                                         EventHandler.MSG_CAS_STATUS_EVENT, status, arg));
469                     }
470                 }
471             };
472 
473     private final TunerResourceManager.ResourcesReclaimListener mResourceListener =
474             new TunerResourceManager.ResourcesReclaimListener() {
475             @Override
476             public void onReclaimResources() {
477                 synchronized (mSessionMap) {
478                     List<Session> sessionList = new ArrayList<>(mSessionMap.keySet());
479                     for (Session casSession: sessionList) {
480                         casSession.close();
481                     }
482                 }
483                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
484                         EventHandler.MSG_CAS_RESOURCE_LOST));
485             }
486         };
487 
488     /**
489      * Describe a CAS plugin with its CA_system_ID and string name.
490      *
491      * Returned as results of {@link #enumeratePlugins}.
492      *
493      */
494     public static class PluginDescriptor {
495         private final int mCASystemId;
496         private final String mName;
497 
PluginDescriptor()498         private PluginDescriptor() {
499             mCASystemId = 0xffff;
500             mName = null;
501         }
502 
PluginDescriptor(@onNull AidlCasPluginDescriptor descriptor)503         PluginDescriptor(@NonNull AidlCasPluginDescriptor descriptor) {
504             mCASystemId = descriptor.caSystemId;
505             mName = descriptor.name;
506         }
507 
PluginDescriptor(@onNull HidlCasPluginDescriptor descriptor)508         PluginDescriptor(@NonNull HidlCasPluginDescriptor descriptor) {
509             mCASystemId = descriptor.caSystemId;
510             mName = descriptor.name;
511         }
512 
getSystemId()513         public int getSystemId() {
514             return mCASystemId;
515         }
516 
517         @NonNull
getName()518         public String getName() {
519             return mName;
520         }
521 
522         @Override
toString()523         public String toString() {
524             return "PluginDescriptor {" + mCASystemId + ", " + mName + "}";
525         }
526     }
527 
toByteArray(@onNull byte[] data, int offset, int length)528     private ArrayList<Byte> toByteArray(@NonNull byte[] data, int offset, int length) {
529         ArrayList<Byte> byteArray = new ArrayList<Byte>(length);
530         for (int i = 0; i < length; i++) {
531             byteArray.add(Byte.valueOf(data[offset + i]));
532         }
533         return byteArray;
534     }
535 
toByteArray(@ullable byte[] data)536     private ArrayList<Byte> toByteArray(@Nullable byte[] data) {
537         if (data == null) {
538             return new ArrayList<Byte>();
539         }
540         return toByteArray(data, 0, data.length);
541     }
542 
toBytes(@onNull ArrayList<Byte> byteArray)543     private byte[] toBytes(@NonNull ArrayList<Byte> byteArray) {
544         byte[] data = null;
545         if (byteArray != null) {
546             data = new byte[byteArray.size()];
547             for (int i = 0; i < data.length; i++) {
548                 data[i] = byteArray.get(i);
549             }
550         }
551         return data;
552     }
553 
554     /**
555      * Class for an open session with the CA system.
556      */
557     public final class Session implements AutoCloseable {
558         final byte[] mSessionId;
559         boolean mIsClosed = false;
560 
Session(@onNull byte[] sessionId)561         Session(@NonNull byte[] sessionId) {
562             mSessionId = sessionId;
563         }
564 
validateSessionInternalStates()565         private void validateSessionInternalStates() {
566             if (mICas == null && mICasHidl == null) {
567                 throw new IllegalStateException();
568             }
569             if (mIsClosed) {
570                 MediaCasStateException.throwExceptionIfNeeded(Status.ERROR_CAS_SESSION_NOT_OPENED);
571             }
572         }
573 
574         /**
575          * Query if an object equal current Session object.
576          *
577          * @param obj an object to compare to current Session object.
578          *
579          * @return Whether input object equal current Session object.
580          */
equals(Object obj)581         public boolean equals(Object obj) {
582             if (obj instanceof Session) {
583                 return Arrays.equals(mSessionId, ((Session) obj).mSessionId);
584             }
585             return false;
586         }
587 
588         /**
589          * Set the private data for a session.
590          *
591          * @param data byte array of the private data.
592          *
593          * @throws IllegalStateException if the MediaCas instance is not valid.
594          * @throws MediaCasException for CAS-specific errors.
595          * @throws MediaCasStateException for CAS-specific state exceptions.
596          */
setPrivateData(@onNull byte[] data)597         public void setPrivateData(@NonNull byte[] data)
598                 throws MediaCasException {
599             validateSessionInternalStates();
600 
601             try {
602                 if (mICas != null) {
603                     try {
604                         mICas.setSessionPrivateData(mSessionId, data);
605                     } catch (ServiceSpecificException se) {
606                         MediaCasException.throwExceptionIfNeeded(se.errorCode);
607                     }
608                 } else {
609                     MediaCasException.throwExceptionIfNeeded(
610                             mICasHidl.setSessionPrivateData(
611                                     toByteArray(mSessionId), toByteArray(data, 0, data.length)));
612                 }
613             } catch (RemoteException e) {
614                 cleanupAndRethrowIllegalState();
615             }
616         }
617 
618 
619         /**
620          * Send a received ECM packet to the specified session of the CA system.
621          *
622          * @param data byte array of the ECM data.
623          * @param offset position within data where the ECM data begins.
624          * @param length length of the data (starting from offset).
625          *
626          * @throws IllegalStateException if the MediaCas instance is not valid.
627          * @throws MediaCasException for CAS-specific errors.
628          * @throws MediaCasStateException for CAS-specific state exceptions.
629          */
processEcm(@onNull byte[] data, int offset, int length)630         public void processEcm(@NonNull byte[] data, int offset, int length)
631                 throws MediaCasException {
632             validateSessionInternalStates();
633 
634             try {
635                 if (mICas != null) {
636                     try {
637                         mICas.processEcm(
638                                 mSessionId, Arrays.copyOfRange(data, offset, length + offset));
639                     } catch (ServiceSpecificException se) {
640                         MediaCasException.throwExceptionIfNeeded(se.errorCode);
641                     }
642                 } else {
643                     MediaCasException.throwExceptionIfNeeded(
644                             mICasHidl.processEcm(
645                                     toByteArray(mSessionId), toByteArray(data, offset, length)));
646                 }
647             } catch (RemoteException e) {
648                 cleanupAndRethrowIllegalState();
649             }
650         }
651 
652         /**
653          * Send a received ECM packet to the specified session of the CA system.
654          * This is similar to {@link Session#processEcm(byte[], int, int)}
655          * except that the entire byte array is sent.
656          *
657          * @param data byte array of the ECM data.
658          *
659          * @throws IllegalStateException if the MediaCas instance is not valid.
660          * @throws MediaCasException for CAS-specific errors.
661          * @throws MediaCasStateException for CAS-specific state exceptions.
662          */
processEcm(@onNull byte[] data)663         public void processEcm(@NonNull byte[] data) throws MediaCasException {
664             processEcm(data, 0, data.length);
665         }
666 
667         /**
668          * Send a session event to a CA system. The format of the event is
669          * scheme-specific and is opaque to the framework.
670          *
671          * @param event an integer denoting a scheme-specific event to be sent.
672          * @param arg a scheme-specific integer argument for the event.
673          * @param data a byte array containing scheme-specific data for the event.
674          *
675          * @throws IllegalStateException if the MediaCas instance is not valid.
676          * @throws MediaCasException for CAS-specific errors.
677          * @throws MediaCasStateException for CAS-specific state exceptions.
678          */
sendSessionEvent(int event, int arg, @Nullable byte[] data)679         public void sendSessionEvent(int event, int arg, @Nullable byte[] data)
680                 throws MediaCasException {
681             validateSessionInternalStates();
682             if (mICas != null) {
683                 try {
684                     if (data == null) {
685                         data = new byte[0];
686                     }
687                     mICas.sendSessionEvent(mSessionId, event, arg, data);
688                 } catch (RemoteException e) {
689                     cleanupAndRethrowIllegalState();
690                 }
691             } else {
692                 if (mICasHidl11 == null) {
693                     Log.d(TAG, "Send Session Event isn't supported by cas@1.0 interface");
694                     throw new UnsupportedCasException("Send Session Event is not supported");
695                 }
696 
697                 try {
698                     MediaCasException.throwExceptionIfNeeded(
699                             mICasHidl11.sendSessionEvent(
700                                     toByteArray(mSessionId), event, arg, toByteArray(data)));
701                 } catch (RemoteException e) {
702                     cleanupAndRethrowIllegalState();
703                 }
704             }
705         }
706 
707         /**
708          * Get Session Id.
709          *
710          * @return session Id of the session.
711          *
712          * @throws IllegalStateException if the MediaCas instance is not valid.
713          */
714         @NonNull
getSessionId()715         public byte[] getSessionId() {
716             validateSessionInternalStates();
717             return mSessionId;
718         }
719 
720         /**
721          * Close the session.
722          *
723          * @throws IllegalStateException if the MediaCas instance is not valid.
724          * @throws MediaCasStateException for CAS-specific state exceptions.
725          */
726         @Override
close()727         public void close() {
728             validateSessionInternalStates();
729             try {
730                 if (mICas != null) {
731                     mICas.closeSession(mSessionId);
732                 } else {
733                     MediaCasStateException.throwExceptionIfNeeded(
734                             mICasHidl.closeSession(toByteArray(mSessionId)));
735                 }
736                 mIsClosed = true;
737                 removeSessionFromResourceMap(this);
738             } catch (RemoteException e) {
739                 cleanupAndRethrowIllegalState();
740             }
741         }
742     }
743 
createFromSessionId(byte[] sessionId)744     Session createFromSessionId(byte[] sessionId) {
745         if (sessionId == null || sessionId.length == 0) {
746             return null;
747         }
748         return new Session(sessionId);
749     }
750 
751     /**
752      * Query if a certain CA system is supported on this device.
753      *
754      * @param CA_system_id the id of the CA system.
755      *
756      * @return Whether the specified CA system is supported on this device.
757      */
isSystemIdSupported(int CA_system_id)758     public static boolean isSystemIdSupported(int CA_system_id) {
759         IMediaCasService service = sService.get();
760         if (service != null) {
761             try {
762                 return service.isSystemIdSupported(CA_system_id);
763             } catch (RemoteException e) {
764                 return false;
765             }
766         }
767 
768         android.hardware.cas.V1_0.IMediaCasService serviceHidl = sServiceHidl.get();
769         if (serviceHidl != null) {
770             try {
771                 return serviceHidl.isSystemIdSupported(CA_system_id);
772             } catch (RemoteException e) {
773             }
774         }
775         return false;
776     }
777 
778     /**
779      * List all available CA plugins on the device.
780      *
781      * @return an array of descriptors for the available CA plugins.
782      */
enumeratePlugins()783     public static PluginDescriptor[] enumeratePlugins() {
784         IMediaCasService service = sService.get();
785         if (service != null) {
786             try {
787                 AidlCasPluginDescriptor[] descriptors = service.enumeratePlugins();
788                 if (descriptors.length == 0) {
789                     return null;
790                 }
791                 PluginDescriptor[] results = new PluginDescriptor[descriptors.length];
792                 for (int i = 0; i < results.length; i++) {
793                     results[i] = new PluginDescriptor(descriptors[i]);
794                 }
795                 return results;
796             } catch (RemoteException e) {
797             }
798         }
799 
800         android.hardware.cas.V1_0.IMediaCasService serviceHidl = sServiceHidl.get();
801         if (serviceHidl != null) {
802             try {
803                 ArrayList<HidlCasPluginDescriptor> descriptors = serviceHidl.enumeratePlugins();
804                 if (descriptors.size() == 0) {
805                     return null;
806                 }
807                 PluginDescriptor[] results = new PluginDescriptor[descriptors.size()];
808                 for (int i = 0; i < results.length; i++) {
809                     results[i] = new PluginDescriptor(descriptors.get(i));
810                 }
811                 return results;
812             } catch (RemoteException e) {
813             }
814         }
815         return null;
816     }
817 
createPlugin(int casSystemId)818     private void createPlugin(int casSystemId) throws UnsupportedCasException {
819         try {
820             mCasSystemId = casSystemId;
821             mUserId = Process.myUid();
822             IMediaCasService service = getService();
823             if (service != null) {
824                 Log.d(TAG, "Use CAS AIDL interface to create plugin");
825                 mICas = service.createPlugin(casSystemId, mBinder);
826             } else {
827                 android.hardware.cas.V1_0.IMediaCasService serviceV10 = getServiceHidl();
828                 android.hardware.cas.V1_2.IMediaCasService serviceV12 =
829                         android.hardware.cas.V1_2.IMediaCasService.castFrom(serviceV10);
830                 if (serviceV12 == null) {
831                     android.hardware.cas.V1_1.IMediaCasService serviceV11 =
832                             android.hardware.cas.V1_1.IMediaCasService.castFrom(serviceV10);
833                     if (serviceV11 == null) {
834                     Log.d(TAG, "Used cas@1_0 interface to create plugin");
835                         mICasHidl = serviceV10.createPlugin(casSystemId, mBinderHidl);
836                     } else {
837                     Log.d(TAG, "Used cas@1.1 interface to create plugin");
838                         mICasHidl =
839                                 mICasHidl11 = serviceV11.createPluginExt(casSystemId, mBinderHidl);
840                     }
841                 } else {
842                     Log.d(TAG, "Used cas@1.2 interface to create plugin");
843                     mICasHidl =
844                             mICasHidl11 =
845                                     mICasHidl12 =
846                                             android.hardware.cas.V1_2.ICas.castFrom(
847                                                     serviceV12.createPluginExt(
848                                                             casSystemId, mBinderHidl));
849                 }
850             }
851         } catch(Exception e) {
852             Log.e(TAG, "Failed to create plugin: " + e);
853             mICas = null;
854             mICasHidl = null;
855         } finally {
856             if (mICas == null && mICasHidl == null) {
857                 throw new UnsupportedCasException(
858                     "Unsupported casSystemId " + casSystemId);
859             }
860         }
861     }
862 
registerClient(@onNull Context context, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint)863     private void registerClient(@NonNull Context context,
864             @Nullable String tvInputServiceSessionId,  @PriorityHintUseCaseType int priorityHint)  {
865 
866         mTunerResourceManager = (TunerResourceManager)
867             context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
868         if (mTunerResourceManager != null) {
869             int[] clientId = new int[1];
870             ResourceClientProfile profile = new ResourceClientProfile();
871             profile.tvInputSessionId = tvInputServiceSessionId;
872             profile.useCase = priorityHint;
873             mTunerResourceManager.registerClientProfile(
874                     profile, context.getMainExecutor(), mResourceListener, clientId);
875             mClientId = clientId[0];
876         }
877     }
878     /**
879      * Instantiate a CA system of the specified system id.
880      *
881      * @param casSystemId The system id of the CA system.
882      *
883      * @throws UnsupportedCasException if the device does not support the
884      * specified CA system.
885      */
MediaCas(int casSystemId)886     public MediaCas(int casSystemId) throws UnsupportedCasException {
887         createPlugin(casSystemId);
888     }
889 
890     /**
891      * Instantiate a CA system of the specified system id.
892      *
893      * @param context the context of the caller.
894      * @param casSystemId The system id of the CA system.
895      * @param tvInputServiceSessionId The Id of the session opened in TV Input Service (TIS)
896      *        {@link android.media.tv.TvInputService#onCreateSession(String, String)}
897      * @param priorityHint priority hint from the use case type for new created CAS system.
898      *
899      * @throws UnsupportedCasException if the device does not support the
900      * specified CA system.
901      */
MediaCas(@onNull Context context, int casSystemId, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint)902     public MediaCas(@NonNull Context context, int casSystemId,
903             @Nullable String tvInputServiceSessionId,
904             @PriorityHintUseCaseType int priorityHint) throws UnsupportedCasException {
905         Objects.requireNonNull(context, "context must not be null");
906         createPlugin(casSystemId);
907         registerClient(context, tvInputServiceSessionId, priorityHint);
908     }
909     /**
910      * Instantiate a CA system of the specified system id with EvenListener.
911      *
912      * @param context the context of the caller.
913      * @param casSystemId The system id of the CA system.
914      * @param tvInputServiceSessionId The Id of the session opened in TV Input Service (TIS)
915      *        {@link android.media.tv.TvInputService#onCreateSession(String, String)}
916      * @param priorityHint priority hint from the use case type for new created CAS system.
917      * @param listener the event listener to be set.
918      * @param handler the handler whose looper the event listener will be called on.
919      * If handler is null, we'll try to use current thread's looper, or the main
920      * looper. If neither are available, an internal thread will be created instead.
921      *
922      * @throws UnsupportedCasException if the device does not support the
923      * specified CA system.
924      */
MediaCas(@onNull Context context, int casSystemId, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint, @Nullable Handler handler, @Nullable EventListener listener)925     public MediaCas(@NonNull Context context, int casSystemId,
926             @Nullable String tvInputServiceSessionId,
927             @PriorityHintUseCaseType int priorityHint,
928             @Nullable Handler handler, @Nullable EventListener listener)
929             throws UnsupportedCasException {
930         Objects.requireNonNull(context, "context must not be null");
931         setEventListener(listener, handler);
932         createPlugin(casSystemId);
933         registerClient(context, tvInputServiceSessionId, priorityHint);
934     }
935 
getBinder()936     IHwBinder getBinder() {
937         if (mICas != null) {
938             return null; // Return IHwBinder only for HIDL
939         }
940 
941         validateInternalStates();
942 
943         return mICasHidl.asBinder();
944     }
945 
946     /**
947      * Check if the HAL is an AIDL implementation. For CTS testing purpose.
948      *
949      * @hide
950      */
951     @TestApi
isAidlHal()952     public boolean isAidlHal() {
953         return mICas != null;
954     }
955 
956     /**
957      * An interface registered by the caller to {@link #setEventListener}
958      * to receives scheme-specific notifications from a MediaCas instance.
959      */
960     public interface EventListener {
961 
962         /**
963          * Notify the listener of a scheme-specific event from the CA system.
964          *
965          * @param mediaCas the MediaCas object to receive this event.
966          * @param event an integer whose meaning is scheme-specific.
967          * @param arg an integer whose meaning is scheme-specific.
968          * @param data a byte array of data whose format and meaning are
969          * scheme-specific.
970          */
onEvent(@onNull MediaCas mediaCas, int event, int arg, @Nullable byte[] data)971         void onEvent(@NonNull MediaCas mediaCas, int event, int arg, @Nullable byte[] data);
972 
973         /**
974          * Notify the listener of a scheme-specific session event from CA system.
975          *
976          * @param mediaCas the MediaCas object to receive this event.
977          * @param session session object which the event is for.
978          * @param event an integer whose meaning is scheme-specific.
979          * @param arg an integer whose meaning is scheme-specific.
980          * @param data a byte array of data whose format and meaning are
981          * scheme-specific.
982          */
onSessionEvent(@onNull MediaCas mediaCas, @NonNull Session session, int event, int arg, @Nullable byte[] data)983         default void onSessionEvent(@NonNull MediaCas mediaCas, @NonNull Session session,
984                 int event, int arg, @Nullable byte[] data) {
985             Log.d(TAG, "Received MediaCas Session event");
986         }
987 
988         /**
989          * Notify the listener that the cas plugin status is updated.
990          *
991          * @param mediaCas the MediaCas object to receive this event.
992          * @param status the plugin status which is updated.
993          * @param arg an integer whose meaning is specific to the status to be updated.
994          */
onPluginStatusUpdate(@onNull MediaCas mediaCas, @PluginStatus int status, int arg)995         default void onPluginStatusUpdate(@NonNull MediaCas mediaCas, @PluginStatus int status,
996                 int arg) {
997             Log.d(TAG, "Received MediaCas Plugin Status event");
998         }
999 
1000         /**
1001          * Notify the listener that the session resources was lost.
1002          *
1003          * @param mediaCas the MediaCas object to receive this event.
1004          */
onResourceLost(@onNull MediaCas mediaCas)1005         default void onResourceLost(@NonNull MediaCas mediaCas) {
1006             Log.d(TAG, "Received MediaCas Resource Reclaim event");
1007         }
1008     }
1009 
1010     /**
1011      * Set an event listener to receive notifications from the MediaCas instance.
1012      *
1013      * @param listener the event listener to be set.
1014      * @param handler the handler whose looper the event listener will be called on.
1015      * If handler is null, we'll try to use current thread's looper, or the main
1016      * looper. If neither are available, an internal thread will be created instead.
1017      */
setEventListener( @ullable EventListener listener, @Nullable Handler handler)1018     public void setEventListener(
1019             @Nullable EventListener listener, @Nullable Handler handler) {
1020         mListener = listener;
1021 
1022         if (mListener == null) {
1023             mEventHandler = null;
1024             return;
1025         }
1026 
1027         Looper looper = (handler != null) ? handler.getLooper() : null;
1028         if (looper == null
1029                 && (looper = Looper.myLooper()) == null
1030                 && (looper = Looper.getMainLooper()) == null) {
1031             if (mHandlerThread == null || !mHandlerThread.isAlive()) {
1032                 mHandlerThread = new HandlerThread("MediaCasEventThread",
1033                         Process.THREAD_PRIORITY_FOREGROUND);
1034                 mHandlerThread.start();
1035             }
1036             looper = mHandlerThread.getLooper();
1037         }
1038         mEventHandler = new EventHandler(looper);
1039     }
1040 
1041     /**
1042      * Send the private data for the CA system.
1043      *
1044      * @param data byte array of the private data.
1045      *
1046      * @throws IllegalStateException if the MediaCas instance is not valid.
1047      * @throws MediaCasException for CAS-specific errors.
1048      * @throws MediaCasStateException for CAS-specific state exceptions.
1049      */
setPrivateData(@onNull byte[] data)1050     public void setPrivateData(@NonNull byte[] data) throws MediaCasException {
1051         validateInternalStates();
1052 
1053         try {
1054             if (mICas != null) {
1055                 try {
1056                     mICas.setPrivateData(data);
1057                 } catch (ServiceSpecificException se) {
1058                     MediaCasException.throwExceptionIfNeeded(se.errorCode);
1059                 }
1060             } else {
1061                 MediaCasException.throwExceptionIfNeeded(
1062                         mICasHidl.setPrivateData(toByteArray(data, 0, data.length)));
1063             }
1064         } catch (RemoteException e) {
1065             cleanupAndRethrowIllegalState();
1066         }
1067     }
1068 
1069     private class OpenSessionCallback implements android.hardware.cas.V1_1.ICas.openSessionCallback{
1070         public Session mSession;
1071         public int mStatus;
1072         @Override
onValues(int status, ArrayList<Byte> sessionId)1073         public void onValues(int status, ArrayList<Byte> sessionId) {
1074             mStatus = status;
1075             mSession = createFromSessionId(toBytes(sessionId));
1076         }
1077     }
1078 
1079     private class OpenSession_1_2_Callback implements
1080             android.hardware.cas.V1_2.ICas.openSession_1_2Callback {
1081 
1082         public Session mSession;
1083         public int mStatus;
1084 
1085         @Override
onValues(int status, ArrayList<Byte> sessionId)1086         public void onValues(int status, ArrayList<Byte> sessionId) {
1087             mStatus = status;
1088             mSession = createFromSessionId(toBytes(sessionId));
1089         }
1090     }
1091 
getSessionResourceHandle()1092     private int getSessionResourceHandle() throws MediaCasException {
1093         validateInternalStates();
1094 
1095         int[] sessionResourceHandle = new int[1];
1096         sessionResourceHandle[0] = -1;
1097         if (mTunerResourceManager != null) {
1098             CasSessionRequest casSessionRequest = new CasSessionRequest();
1099             casSessionRequest.clientId = mClientId;
1100             casSessionRequest.casSystemId = mCasSystemId;
1101             if (!mTunerResourceManager
1102                     .requestCasSession(casSessionRequest, sessionResourceHandle)) {
1103                 throw new MediaCasException.InsufficientResourceException(
1104                     "insufficient resource to Open Session");
1105             }
1106         }
1107         return sessionResourceHandle[0];
1108     }
1109 
addSessionToResourceMap(Session session, int sessionResourceHandle)1110     private void addSessionToResourceMap(Session session, int sessionResourceHandle) {
1111 
1112         if (sessionResourceHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
1113             synchronized (mSessionMap) {
1114                 mSessionMap.put(session, sessionResourceHandle);
1115             }
1116         }
1117     }
1118 
removeSessionFromResourceMap(Session session)1119     private void removeSessionFromResourceMap(Session session) {
1120 
1121         synchronized (mSessionMap) {
1122             if (mSessionMap.get(session) != null) {
1123                 mTunerResourceManager.releaseCasSession(mSessionMap.get(session), mClientId);
1124                 mSessionMap.remove(session);
1125             }
1126         }
1127     }
1128 
1129     /**
1130      * Open a session to descramble one or more streams scrambled by the
1131      * conditional access system.
1132      *
1133      * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
1134      * to get cas session resource if cas session resources is limited. If the client can't get the
1135      * resource, this call returns {@link MediaCasException.InsufficientResourceException }.
1136      *
1137      * @return session the newly opened session.
1138      *
1139      * @throws IllegalStateException if the MediaCas instance is not valid.
1140      * @throws MediaCasException for CAS-specific errors.
1141      * @throws MediaCasStateException for CAS-specific state exceptions.
1142      */
openSession()1143     public Session openSession() throws MediaCasException {
1144         int sessionResourceHandle = getSessionResourceHandle();
1145 
1146         try {
1147             if (mICas != null) {
1148                 try {
1149                     byte[] sessionId = mICas.openSessionDefault();
1150                     Session session = createFromSessionId(sessionId);
1151                     Log.d(TAG, "Write Stats Log for succeed to Open Session.");
1152                     FrameworkStatsLog.write(
1153                             FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS,
1154                             mUserId,
1155                             mCasSystemId,
1156                             FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED);
1157                     return session;
1158                 } catch (ServiceSpecificException se) {
1159                     MediaCasException.throwExceptionIfNeeded(se.errorCode);
1160                 }
1161             } else if (mICasHidl != null) {
1162                 OpenSessionCallback cb = new OpenSessionCallback();
1163                 mICasHidl.openSession(cb);
1164                 MediaCasException.throwExceptionIfNeeded(cb.mStatus);
1165                 addSessionToResourceMap(cb.mSession, sessionResourceHandle);
1166                 Log.d(TAG, "Write Stats Log for succeed to Open Session.");
1167                 FrameworkStatsLog.write(
1168                         FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS,
1169                         mUserId,
1170                         mCasSystemId,
1171                         FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED);
1172                 return cb.mSession;
1173             }
1174         } catch (RemoteException e) {
1175             cleanupAndRethrowIllegalState();
1176         }
1177         Log.d(TAG, "Write Stats Log for fail to Open Session.");
1178         FrameworkStatsLog
1179                 .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId,
1180                     FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED);
1181         return null;
1182     }
1183 
1184     /**
1185      * Open a session with usage and scrambling information, so that descrambler can be configured
1186      * to descramble one or more streams scrambled by the conditional access system.
1187      *
1188      * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
1189      * to get cas session resource if cas session resources is limited. If the client can't get the
1190      * resource, this call returns {@link MediaCasException.InsufficientResourceException}.
1191      *
1192      * @param sessionUsage used for the created session.
1193      * @param scramblingMode used for the created session.
1194      *
1195      * @return session the newly opened session.
1196      *
1197      * @throws IllegalStateException if the MediaCas instance is not valid.
1198      * @throws MediaCasException for CAS-specific errors.
1199      * @throws MediaCasStateException for CAS-specific state exceptions.
1200      */
1201     @Nullable
openSession(@essionUsage int sessionUsage, @ScramblingMode int scramblingMode)1202     public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode)
1203             throws MediaCasException {
1204         int sessionResourceHandle = getSessionResourceHandle();
1205 
1206         if (mICas != null) {
1207             try {
1208                 byte[] sessionId = mICas.openSession(sessionUsage, scramblingMode);
1209                 Session session = createFromSessionId(sessionId);
1210                 addSessionToResourceMap(session, sessionResourceHandle);
1211                 Log.d(TAG, "Write Stats Log for succeed to Open Session.");
1212                 FrameworkStatsLog.write(
1213                         FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS,
1214                         mUserId,
1215                         mCasSystemId,
1216                         FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED);
1217                 return session;
1218             } catch (ServiceSpecificException | RemoteException e) {
1219                 cleanupAndRethrowIllegalState();
1220             }
1221         }
1222         if (mICasHidl12 == null) {
1223             Log.d(TAG, "Open Session with scrambling mode is only supported by cas@1.2+ interface");
1224             throw new UnsupportedCasException("Open Session with scrambling mode is not supported");
1225         }
1226 
1227         try {
1228             OpenSession_1_2_Callback cb = new OpenSession_1_2_Callback();
1229             mICasHidl12.openSession_1_2(sessionUsage, scramblingMode, cb);
1230             MediaCasException.throwExceptionIfNeeded(cb.mStatus);
1231             addSessionToResourceMap(cb.mSession, sessionResourceHandle);
1232             Log.d(TAG, "Write Stats Log for succeed to Open Session.");
1233             FrameworkStatsLog
1234                     .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId,
1235                         FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED);
1236             return cb.mSession;
1237         } catch (RemoteException e) {
1238             cleanupAndRethrowIllegalState();
1239         }
1240         Log.d(TAG, "Write Stats Log for fail to Open Session.");
1241         FrameworkStatsLog
1242                 .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId,
1243                     FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED);
1244         return null;
1245     }
1246 
1247     /**
1248      * Send a received EMM packet to the CA system.
1249      *
1250      * @param data byte array of the EMM data.
1251      * @param offset position within data where the EMM data begins.
1252      * @param length length of the data (starting from offset).
1253      *
1254      * @throws IllegalStateException if the MediaCas instance is not valid.
1255      * @throws MediaCasException for CAS-specific errors.
1256      * @throws MediaCasStateException for CAS-specific state exceptions.
1257      */
processEmm(@onNull byte[] data, int offset, int length)1258     public void processEmm(@NonNull byte[] data, int offset, int length)
1259             throws MediaCasException {
1260         validateInternalStates();
1261 
1262         try {
1263             if (mICas != null) {
1264                 try {
1265                     mICas.processEmm(Arrays.copyOfRange(data, offset, length));
1266                 } catch (ServiceSpecificException se) {
1267                     MediaCasException.throwExceptionIfNeeded(se.errorCode);
1268                 }
1269             } else {
1270                 MediaCasException.throwExceptionIfNeeded(
1271                         mICasHidl.processEmm(toByteArray(data, offset, length)));
1272             }
1273         } catch (RemoteException e) {
1274             cleanupAndRethrowIllegalState();
1275         }
1276     }
1277 
1278     /**
1279      * Send a received EMM packet to the CA system. This is similar to
1280      * {@link #processEmm(byte[], int, int)} except that the entire byte
1281      * array is sent.
1282      *
1283      * @param data byte array of the EMM data.
1284      *
1285      * @throws IllegalStateException if the MediaCas instance is not valid.
1286      * @throws MediaCasException for CAS-specific errors.
1287      * @throws MediaCasStateException for CAS-specific state exceptions.
1288      */
processEmm(@onNull byte[] data)1289     public void processEmm(@NonNull byte[] data) throws MediaCasException {
1290         processEmm(data, 0, data.length);
1291     }
1292 
1293     /**
1294      * Send an event to a CA system. The format of the event is scheme-specific
1295      * and is opaque to the framework.
1296      *
1297      * @param event an integer denoting a scheme-specific event to be sent.
1298      * @param arg a scheme-specific integer argument for the event.
1299      * @param data a byte array containing scheme-specific data for the event.
1300      *
1301      * @throws IllegalStateException if the MediaCas instance is not valid.
1302      * @throws MediaCasException for CAS-specific errors.
1303      * @throws MediaCasStateException for CAS-specific state exceptions.
1304      */
sendEvent(int event, int arg, @Nullable byte[] data)1305     public void sendEvent(int event, int arg, @Nullable byte[] data)
1306             throws MediaCasException {
1307         validateInternalStates();
1308 
1309         try {
1310             if (mICas != null) {
1311                 try {
1312                     if (data == null) {
1313                         data = new byte[0];
1314                     }
1315                     mICas.sendEvent(event, arg, data);
1316                 } catch (ServiceSpecificException se) {
1317                     MediaCasException.throwExceptionIfNeeded(se.errorCode);
1318                 }
1319             } else {
1320                 MediaCasException.throwExceptionIfNeeded(
1321                         mICasHidl.sendEvent(event, arg, toByteArray(data)));
1322             }
1323         } catch (RemoteException e) {
1324             cleanupAndRethrowIllegalState();
1325         }
1326     }
1327 
1328    /**
1329      * Initiate a provisioning operation for a CA system.
1330      *
1331      * @param provisionString string containing information needed for the
1332      * provisioning operation, the format of which is scheme and implementation
1333      * specific.
1334      *
1335      * @throws IllegalStateException if the MediaCas instance is not valid.
1336      * @throws MediaCasException for CAS-specific errors.
1337      * @throws MediaCasStateException for CAS-specific state exceptions.
1338      */
provision(@onNull String provisionString)1339     public void provision(@NonNull String provisionString) throws MediaCasException {
1340         validateInternalStates();
1341 
1342         try {
1343             if (mICas != null) {
1344                 try {
1345                     mICas.provision(provisionString);
1346                 } catch (ServiceSpecificException se) {
1347                     MediaCasException.throwExceptionIfNeeded(se.errorCode);
1348                 }
1349             } else {
1350                 MediaCasException.throwExceptionIfNeeded(mICasHidl.provision(provisionString));
1351             }
1352         } catch (RemoteException e) {
1353             cleanupAndRethrowIllegalState();
1354         }
1355     }
1356 
1357     /**
1358      * Notify the CA system to refresh entitlement keys.
1359      *
1360      * @param refreshType the type of the refreshment.
1361      * @param refreshData private data associated with the refreshment.
1362      *
1363      * @throws IllegalStateException if the MediaCas instance is not valid.
1364      * @throws MediaCasException for CAS-specific errors.
1365      * @throws MediaCasStateException for CAS-specific state exceptions.
1366      */
refreshEntitlements(int refreshType, @Nullable byte[] refreshData)1367     public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData)
1368             throws MediaCasException {
1369         validateInternalStates();
1370 
1371         try {
1372             if (mICas != null) {
1373                 try {
1374                     if (refreshData == null) {
1375                         refreshData = new byte[0];
1376                     }
1377                     mICas.refreshEntitlements(refreshType, refreshData);
1378                 } catch (ServiceSpecificException se) {
1379                     MediaCasException.throwExceptionIfNeeded(se.errorCode);
1380                 }
1381             } else {
1382                 MediaCasException.throwExceptionIfNeeded(
1383                         mICasHidl.refreshEntitlements(refreshType, toByteArray(refreshData)));
1384             }
1385         } catch (RemoteException e) {
1386             cleanupAndRethrowIllegalState();
1387         }
1388     }
1389 
1390     /**
1391      * Release Cas session. This is primarily used as a test API for CTS.
1392      * @hide
1393      */
1394     @TestApi
forceResourceLost()1395     public void forceResourceLost() {
1396         if (mResourceListener != null) {
1397             mResourceListener.onReclaimResources();
1398         }
1399     }
1400 
1401     @Override
close()1402     public void close() {
1403         if (mICas != null) {
1404             try {
1405                 mICas.release();
1406             } catch (RemoteException e) {
1407             } finally {
1408                 mICas = null;
1409             }
1410         } else if (mICasHidl != null) {
1411             try {
1412                 mICasHidl.release();
1413             } catch (RemoteException e) {
1414             } finally {
1415                 mICasHidl = mICasHidl11 = mICasHidl12 = null;
1416             }
1417         }
1418 
1419         if (mTunerResourceManager != null) {
1420             mTunerResourceManager.unregisterClientProfile(mClientId);
1421             mTunerResourceManager = null;
1422         }
1423 
1424         if (mHandlerThread != null) {
1425             mHandlerThread.quit();
1426             mHandlerThread = null;
1427         }
1428     }
1429 
1430     @Override
finalize()1431     protected void finalize() {
1432         close();
1433     }
1434 }
1435