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.IBinder;
39 import android.os.IHwBinder;
40 import android.os.Looper;
41 import android.os.Message;
42 import android.os.Process;
43 import android.os.RemoteException;
44 import android.os.ServiceManager;
45 import android.os.ServiceSpecificException;
46 import android.util.Log;
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 IMediaCasService sService = null;
268     private static Object sAidlLock = new Object();
269 
270     /** DeathListener for AIDL service */
271     private static IBinder.DeathRecipient sDeathListener =
272             new IBinder.DeathRecipient() {
273                 @Override
274                 public void binderDied() {
275                     synchronized (sAidlLock) {
276                         Log.d(TAG, "The service is dead");
277                         sService.asBinder().unlinkToDeath(sDeathListener, 0);
278                         sService = null;
279                     }
280                 }
281             };
282 
getService()283     static IMediaCasService getService() {
284         synchronized (sAidlLock) {
285             if (sService == null || !sService.asBinder().isBinderAlive()) {
286                 try {
287                     Log.d(TAG, "Trying to get AIDL service");
288                     sService =
289                             IMediaCasService.Stub.asInterface(
290                                     ServiceManager.waitForDeclaredService(
291                                             IMediaCasService.DESCRIPTOR + "/default"));
292                     if (sService != null) {
293                         sService.asBinder().linkToDeath(sDeathListener, 0);
294                     }
295                 } catch (Exception eAidl) {
296                     Log.d(TAG, "Failed to get cas AIDL service");
297                 }
298             }
299             return sService;
300         }
301     }
302 
303     private static android.hardware.cas.V1_0.IMediaCasService sServiceHidl = null;
304     private static Object sHidlLock = new Object();
305 
306     /** Used to indicate the right end-point to handle the serviceDied method */
307     private static final long MEDIA_CAS_HIDL_COOKIE = 394;
308 
309     /** DeathListener for HIDL service */
310     private static IHwBinder.DeathRecipient sDeathListenerHidl =
311             new IHwBinder.DeathRecipient() {
312                 @Override
313                 public void serviceDied(long cookie) {
314                     if (cookie == MEDIA_CAS_HIDL_COOKIE) {
315                         synchronized (sHidlLock) {
316                             sServiceHidl = null;
317                         }
318                     }
319                 }
320             };
321 
getServiceHidl()322     static android.hardware.cas.V1_0.IMediaCasService getServiceHidl() {
323         synchronized (sHidlLock) {
324             if (sServiceHidl != null) {
325                 return sServiceHidl;
326             } else {
327                 try {
328                     Log.d(TAG, "Trying to get cas@1.2 service");
329                     android.hardware.cas.V1_2.IMediaCasService serviceV12 =
330                             android.hardware.cas.V1_2.IMediaCasService.getService(true /*wait*/);
331                     if (serviceV12 != null) {
332                         sServiceHidl = serviceV12;
333                         sServiceHidl.linkToDeath(sDeathListenerHidl, MEDIA_CAS_HIDL_COOKIE);
334                         return sServiceHidl;
335                     }
336                 } catch (Exception eV1_2) {
337                     Log.d(TAG, "Failed to get cas@1.2 service");
338                 }
339 
340                 try {
341                     Log.d(TAG, "Trying to get cas@1.1 service");
342                     android.hardware.cas.V1_1.IMediaCasService serviceV11 =
343                             android.hardware.cas.V1_1.IMediaCasService.getService(true /*wait*/);
344                     if (serviceV11 != null) {
345                         sServiceHidl = serviceV11;
346                         sServiceHidl.linkToDeath(sDeathListenerHidl, MEDIA_CAS_HIDL_COOKIE);
347                         return sServiceHidl;
348                     }
349                 } catch (Exception eV1_1) {
350                     Log.d(TAG, "Failed to get cas@1.1 service");
351                 }
352 
353                 try {
354                     Log.d(TAG, "Trying to get cas@1.0 service");
355                     sServiceHidl =
356                             android.hardware.cas.V1_0.IMediaCasService.getService(true /*wait*/);
357                     if (sServiceHidl != null) {
358                         sServiceHidl.linkToDeath(sDeathListenerHidl, MEDIA_CAS_HIDL_COOKIE);
359                     }
360                     return sServiceHidl;
361                 } catch (Exception eV1_0) {
362                     Log.d(TAG, "Failed to get cas@1.0 service");
363                 }
364             }
365         }
366         // Couldn't find an HIDL service, returning null.
367         return null;
368     }
369 
validateInternalStates()370     private void validateInternalStates() {
371         if (mICas == null && mICasHidl == null) {
372             throw new IllegalStateException();
373         }
374     }
375 
cleanupAndRethrowIllegalState()376     private void cleanupAndRethrowIllegalState() {
377         mICas = null;
378         mICasHidl = null;
379         mICasHidl11 = null;
380         mICasHidl12 = null;
381         throw new IllegalStateException();
382     }
383 
384     private class EventHandler extends Handler {
385 
386         private static final int MSG_CAS_EVENT = 0;
387         private static final int MSG_CAS_SESSION_EVENT = 1;
388         private static final int MSG_CAS_STATUS_EVENT = 2;
389         private static final int MSG_CAS_RESOURCE_LOST = 3;
390         private static final String SESSION_KEY = "sessionId";
391         private static final String DATA_KEY = "data";
392 
EventHandler(Looper looper)393         public EventHandler(Looper looper) {
394             super(looper);
395         }
396 
397         @Override
handleMessage(Message msg)398         public void handleMessage(Message msg) {
399             if (msg.what == MSG_CAS_EVENT) {
400                 byte[] data = (msg.obj == null) ? new byte[0] : (byte[]) msg.obj;
401                 mListener.onEvent(MediaCas.this, msg.arg1, msg.arg2, data);
402             } else if (msg.what == MSG_CAS_SESSION_EVENT) {
403                 Bundle bundle = msg.getData();
404                 byte[] sessionId = bundle.getByteArray(SESSION_KEY);
405                 byte[] data = bundle.getByteArray(DATA_KEY);
406                 mListener.onSessionEvent(
407                         MediaCas.this, createFromSessionId(sessionId), msg.arg1, msg.arg2, data);
408             } else if (msg.what == MSG_CAS_STATUS_EVENT) {
409                 if ((msg.arg1 == PLUGIN_STATUS_SESSION_NUMBER_CHANGED)
410                         && (mTunerResourceManager != null)) {
411                     mTunerResourceManager.updateCasInfo(mCasSystemId, msg.arg2);
412                 }
413                 mListener.onPluginStatusUpdate(MediaCas.this, msg.arg1, msg.arg2);
414             } else if (msg.what == MSG_CAS_RESOURCE_LOST) {
415                 mListener.onResourceLost(MediaCas.this);
416             }
417         }
418     }
419 
420     private final ICasListener.Stub mBinder =
421             new ICasListener.Stub() {
422                 @Override
423                 public void onEvent(int event, int arg, byte[] data) throws RemoteException {
424                     if (mEventHandler != null) {
425                         mEventHandler.sendMessage(
426                                 mEventHandler.obtainMessage(
427                                         EventHandler.MSG_CAS_EVENT, event, arg, data));
428                     }
429                 }
430 
431                 @Override
432                 public void onSessionEvent(byte[] sessionId, int event, int arg, byte[] data)
433                         throws RemoteException {
434                     if (mEventHandler != null) {
435                         Message msg = mEventHandler.obtainMessage();
436                         msg.what = EventHandler.MSG_CAS_SESSION_EVENT;
437                         msg.arg1 = event;
438                         msg.arg2 = arg;
439                         Bundle bundle = new Bundle();
440                         bundle.putByteArray(EventHandler.SESSION_KEY, sessionId);
441                         bundle.putByteArray(EventHandler.DATA_KEY, data);
442                         msg.setData(bundle);
443                         mEventHandler.sendMessage(msg);
444                     }
445                 }
446 
447                 @Override
448                 public void onStatusUpdate(byte status, int arg) throws RemoteException {
449                     if (mEventHandler != null) {
450                         mEventHandler.sendMessage(
451                                 mEventHandler.obtainMessage(
452                                         EventHandler.MSG_CAS_STATUS_EVENT, status, arg));
453                     }
454                 }
455 
456                 @Override
457                 public synchronized String getInterfaceHash() throws android.os.RemoteException {
458                     return ICasListener.Stub.HASH;
459                 }
460 
461                 @Override
462                 public int getInterfaceVersion() throws android.os.RemoteException {
463                     return ICasListener.Stub.VERSION;
464                 }
465             };
466 
467     private final android.hardware.cas.V1_2.ICasListener.Stub mBinderHidl =
468             new android.hardware.cas.V1_2.ICasListener.Stub() {
469                 @Override
470                 public void onEvent(int event, int arg, @Nullable ArrayList<Byte> data)
471                         throws RemoteException {
472                     if (mEventHandler != null) {
473                         mEventHandler.sendMessage(
474                                 mEventHandler.obtainMessage(
475                                         EventHandler.MSG_CAS_EVENT, event, arg, toBytes(data)));
476                     }
477                 }
478 
479                 @Override
480                 public void onSessionEvent(
481                         @NonNull ArrayList<Byte> sessionId,
482                         int event,
483                         int arg,
484                         @Nullable ArrayList<Byte> data)
485                         throws RemoteException {
486                     if (mEventHandler != null) {
487                         Message msg = mEventHandler.obtainMessage();
488                         msg.what = EventHandler.MSG_CAS_SESSION_EVENT;
489                         msg.arg1 = event;
490                         msg.arg2 = arg;
491                         Bundle bundle = new Bundle();
492                         bundle.putByteArray(EventHandler.SESSION_KEY, toBytes(sessionId));
493                         bundle.putByteArray(EventHandler.DATA_KEY, toBytes(data));
494                         msg.setData(bundle);
495                         mEventHandler.sendMessage(msg);
496                     }
497                 }
498 
499                 @Override
500                 public void onStatusUpdate(byte status, int arg) throws RemoteException {
501                     if (mEventHandler != null) {
502                         mEventHandler.sendMessage(
503                                 mEventHandler.obtainMessage(
504                                         EventHandler.MSG_CAS_STATUS_EVENT, status, arg));
505                     }
506                 }
507             };
508 
509     private final TunerResourceManager.ResourcesReclaimListener mResourceListener =
510             new TunerResourceManager.ResourcesReclaimListener() {
511             @Override
512             public void onReclaimResources() {
513                 synchronized (mSessionMap) {
514                     List<Session> sessionList = new ArrayList<>(mSessionMap.keySet());
515                     for (Session casSession: sessionList) {
516                         casSession.close();
517                     }
518                 }
519                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
520                         EventHandler.MSG_CAS_RESOURCE_LOST));
521             }
522         };
523 
524     /**
525      * Describe a CAS plugin with its CA_system_ID and string name.
526      *
527      * Returned as results of {@link #enumeratePlugins}.
528      *
529      */
530     public static class PluginDescriptor {
531         private final int mCASystemId;
532         private final String mName;
533 
PluginDescriptor()534         private PluginDescriptor() {
535             mCASystemId = 0xffff;
536             mName = null;
537         }
538 
PluginDescriptor(@onNull AidlCasPluginDescriptor descriptor)539         PluginDescriptor(@NonNull AidlCasPluginDescriptor descriptor) {
540             mCASystemId = descriptor.caSystemId;
541             mName = descriptor.name;
542         }
543 
PluginDescriptor(@onNull HidlCasPluginDescriptor descriptor)544         PluginDescriptor(@NonNull HidlCasPluginDescriptor descriptor) {
545             mCASystemId = descriptor.caSystemId;
546             mName = descriptor.name;
547         }
548 
getSystemId()549         public int getSystemId() {
550             return mCASystemId;
551         }
552 
553         @NonNull
getName()554         public String getName() {
555             return mName;
556         }
557 
558         @Override
toString()559         public String toString() {
560             return "PluginDescriptor {" + mCASystemId + ", " + mName + "}";
561         }
562     }
563 
toByteArray(@onNull byte[] data, int offset, int length)564     private ArrayList<Byte> toByteArray(@NonNull byte[] data, int offset, int length) {
565         ArrayList<Byte> byteArray = new ArrayList<Byte>(length);
566         for (int i = 0; i < length; i++) {
567             byteArray.add(Byte.valueOf(data[offset + i]));
568         }
569         return byteArray;
570     }
571 
toByteArray(@ullable byte[] data)572     private ArrayList<Byte> toByteArray(@Nullable byte[] data) {
573         if (data == null) {
574             return new ArrayList<Byte>();
575         }
576         return toByteArray(data, 0, data.length);
577     }
578 
toBytes(@onNull ArrayList<Byte> byteArray)579     private byte[] toBytes(@NonNull ArrayList<Byte> byteArray) {
580         byte[] data = null;
581         if (byteArray != null) {
582             data = new byte[byteArray.size()];
583             for (int i = 0; i < data.length; i++) {
584                 data[i] = byteArray.get(i);
585             }
586         }
587         return data;
588     }
589 
590     /**
591      * Class for an open session with the CA system.
592      */
593     public final class Session implements AutoCloseable {
594         final byte[] mSessionId;
595         boolean mIsClosed = false;
596 
Session(@onNull byte[] sessionId)597         Session(@NonNull byte[] sessionId) {
598             mSessionId = sessionId;
599         }
600 
validateSessionInternalStates()601         private void validateSessionInternalStates() {
602             if (mICas == null && mICasHidl == null) {
603                 throw new IllegalStateException();
604             }
605             if (mIsClosed) {
606                 MediaCasStateException.throwExceptionIfNeeded(Status.ERROR_CAS_SESSION_NOT_OPENED);
607             }
608         }
609 
610         /**
611          * Query if an object equal current Session object.
612          *
613          * @param obj an object to compare to current Session object.
614          *
615          * @return Whether input object equal current Session object.
616          */
equals(Object obj)617         public boolean equals(Object obj) {
618             if (obj instanceof Session) {
619                 return Arrays.equals(mSessionId, ((Session) obj).mSessionId);
620             }
621             return false;
622         }
623 
624         /**
625          * Set the private data for a session.
626          *
627          * @param data byte array of the private data.
628          *
629          * @throws IllegalStateException if the MediaCas instance is not valid.
630          * @throws MediaCasException for CAS-specific errors.
631          * @throws MediaCasStateException for CAS-specific state exceptions.
632          */
setPrivateData(@onNull byte[] data)633         public void setPrivateData(@NonNull byte[] data)
634                 throws MediaCasException {
635             validateSessionInternalStates();
636 
637             try {
638                 if (mICas != null) {
639                     try {
640                         mICas.setSessionPrivateData(mSessionId, data);
641                     } catch (ServiceSpecificException se) {
642                         MediaCasException.throwExceptionIfNeeded(se.errorCode);
643                     }
644                 } else {
645                     MediaCasException.throwExceptionIfNeeded(
646                             mICasHidl.setSessionPrivateData(
647                                     toByteArray(mSessionId), toByteArray(data, 0, data.length)));
648                 }
649             } catch (RemoteException e) {
650                 cleanupAndRethrowIllegalState();
651             }
652         }
653 
654 
655         /**
656          * Send a received ECM packet to the specified session of the CA system.
657          *
658          * @param data byte array of the ECM data.
659          * @param offset position within data where the ECM data begins.
660          * @param length length of the data (starting from offset).
661          *
662          * @throws IllegalStateException if the MediaCas instance is not valid.
663          * @throws MediaCasException for CAS-specific errors.
664          * @throws MediaCasStateException for CAS-specific state exceptions.
665          */
processEcm(@onNull byte[] data, int offset, int length)666         public void processEcm(@NonNull byte[] data, int offset, int length)
667                 throws MediaCasException {
668             validateSessionInternalStates();
669 
670             try {
671                 if (mICas != null) {
672                     try {
673                         mICas.processEcm(
674                                 mSessionId, Arrays.copyOfRange(data, offset, length + offset));
675                     } catch (ServiceSpecificException se) {
676                         MediaCasException.throwExceptionIfNeeded(se.errorCode);
677                     }
678                 } else {
679                     MediaCasException.throwExceptionIfNeeded(
680                             mICasHidl.processEcm(
681                                     toByteArray(mSessionId), toByteArray(data, offset, length)));
682                 }
683             } catch (RemoteException e) {
684                 cleanupAndRethrowIllegalState();
685             }
686         }
687 
688         /**
689          * Send a received ECM packet to the specified session of the CA system.
690          * This is similar to {@link Session#processEcm(byte[], int, int)}
691          * except that the entire byte array is sent.
692          *
693          * @param data byte array of the ECM data.
694          *
695          * @throws IllegalStateException if the MediaCas instance is not valid.
696          * @throws MediaCasException for CAS-specific errors.
697          * @throws MediaCasStateException for CAS-specific state exceptions.
698          */
processEcm(@onNull byte[] data)699         public void processEcm(@NonNull byte[] data) throws MediaCasException {
700             processEcm(data, 0, data.length);
701         }
702 
703         /**
704          * Send a session event to a CA system. The format of the event is
705          * scheme-specific and is opaque to the framework.
706          *
707          * @param event an integer denoting a scheme-specific event to be sent.
708          * @param arg a scheme-specific integer argument for the event.
709          * @param data a byte array containing scheme-specific data for the event.
710          *
711          * @throws IllegalStateException if the MediaCas instance is not valid.
712          * @throws MediaCasException for CAS-specific errors.
713          * @throws MediaCasStateException for CAS-specific state exceptions.
714          */
sendSessionEvent(int event, int arg, @Nullable byte[] data)715         public void sendSessionEvent(int event, int arg, @Nullable byte[] data)
716                 throws MediaCasException {
717             validateSessionInternalStates();
718             if (mICas != null) {
719                 try {
720                     if (data == null) {
721                         data = new byte[0];
722                     }
723                     mICas.sendSessionEvent(mSessionId, event, arg, data);
724                 } catch (RemoteException e) {
725                     cleanupAndRethrowIllegalState();
726                 }
727             } else {
728                 if (mICasHidl11 == null) {
729                     Log.d(TAG, "Send Session Event isn't supported by cas@1.0 interface");
730                     throw new UnsupportedCasException("Send Session Event is not supported");
731                 }
732 
733                 try {
734                     MediaCasException.throwExceptionIfNeeded(
735                             mICasHidl11.sendSessionEvent(
736                                     toByteArray(mSessionId), event, arg, toByteArray(data)));
737                 } catch (RemoteException e) {
738                     cleanupAndRethrowIllegalState();
739                 }
740             }
741         }
742 
743         /**
744          * Get Session Id.
745          *
746          * @return session Id of the session.
747          *
748          * @throws IllegalStateException if the MediaCas instance is not valid.
749          */
750         @NonNull
getSessionId()751         public byte[] getSessionId() {
752             validateSessionInternalStates();
753             return mSessionId;
754         }
755 
756         /**
757          * Close the session.
758          *
759          * @throws IllegalStateException if the MediaCas instance is not valid.
760          * @throws MediaCasStateException for CAS-specific state exceptions.
761          */
762         @Override
close()763         public void close() {
764             validateSessionInternalStates();
765             try {
766                 if (mICas != null) {
767                     mICas.closeSession(mSessionId);
768                 } else {
769                     MediaCasStateException.throwExceptionIfNeeded(
770                             mICasHidl.closeSession(toByteArray(mSessionId)));
771                 }
772                 mIsClosed = true;
773                 removeSessionFromResourceMap(this);
774             } catch (RemoteException e) {
775                 cleanupAndRethrowIllegalState();
776             }
777         }
778     }
779 
createFromSessionId(byte[] sessionId)780     Session createFromSessionId(byte[] sessionId) {
781         if (sessionId == null || sessionId.length == 0) {
782             return null;
783         }
784         return new Session(sessionId);
785     }
786 
787     /**
788      * Query if a certain CA system is supported on this device.
789      *
790      * @param CA_system_id the id of the CA system.
791      *
792      * @return Whether the specified CA system is supported on this device.
793      */
isSystemIdSupported(int CA_system_id)794     public static boolean isSystemIdSupported(int CA_system_id) {
795         IMediaCasService service = getService();
796         if (service != null) {
797             try {
798                 return service.isSystemIdSupported(CA_system_id);
799             } catch (RemoteException e) {
800                 return false;
801             }
802         }
803 
804         android.hardware.cas.V1_0.IMediaCasService serviceHidl = getServiceHidl();
805         if (serviceHidl != null) {
806             try {
807                 return serviceHidl.isSystemIdSupported(CA_system_id);
808             } catch (RemoteException e) {
809             }
810         }
811         return false;
812     }
813 
814     /**
815      * List all available CA plugins on the device.
816      *
817      * @return an array of descriptors for the available CA plugins.
818      */
enumeratePlugins()819     public static PluginDescriptor[] enumeratePlugins() {
820         IMediaCasService service = getService();
821         if (service != null) {
822             try {
823                 AidlCasPluginDescriptor[] descriptors = service.enumeratePlugins();
824                 if (descriptors.length == 0) {
825                     return null;
826                 }
827                 PluginDescriptor[] results = new PluginDescriptor[descriptors.length];
828                 for (int i = 0; i < results.length; i++) {
829                     results[i] = new PluginDescriptor(descriptors[i]);
830                 }
831                 return results;
832             } catch (RemoteException e) {
833                 Log.e(TAG, "Some exception while enumerating plugins");
834             }
835         }
836 
837         android.hardware.cas.V1_0.IMediaCasService serviceHidl = getServiceHidl();
838         if (serviceHidl != null) {
839             try {
840                 ArrayList<HidlCasPluginDescriptor> descriptors = serviceHidl.enumeratePlugins();
841                 if (descriptors.size() == 0) {
842                     return null;
843                 }
844                 PluginDescriptor[] results = new PluginDescriptor[descriptors.size()];
845                 for (int i = 0; i < results.length; i++) {
846                     results[i] = new PluginDescriptor(descriptors.get(i));
847                 }
848                 return results;
849             } catch (RemoteException e) {
850             }
851         }
852         return null;
853     }
854 
createPlugin(int casSystemId)855     private void createPlugin(int casSystemId) throws UnsupportedCasException {
856         try {
857             mCasSystemId = casSystemId;
858             mUserId = Process.myUid();
859             IMediaCasService service = getService();
860             if (service != null) {
861                 Log.d(TAG, "Use CAS AIDL interface to create plugin");
862                 mICas = service.createPlugin(casSystemId, mBinder);
863             } else {
864                 android.hardware.cas.V1_0.IMediaCasService serviceV10 = getServiceHidl();
865                 android.hardware.cas.V1_2.IMediaCasService serviceV12 =
866                         android.hardware.cas.V1_2.IMediaCasService.castFrom(serviceV10);
867                 if (serviceV12 == null) {
868                     android.hardware.cas.V1_1.IMediaCasService serviceV11 =
869                             android.hardware.cas.V1_1.IMediaCasService.castFrom(serviceV10);
870                     if (serviceV11 == null) {
871                     Log.d(TAG, "Used cas@1_0 interface to create plugin");
872                         mICasHidl = serviceV10.createPlugin(casSystemId, mBinderHidl);
873                     } else {
874                     Log.d(TAG, "Used cas@1.1 interface to create plugin");
875                         mICasHidl =
876                                 mICasHidl11 = serviceV11.createPluginExt(casSystemId, mBinderHidl);
877                     }
878                 } else {
879                     Log.d(TAG, "Used cas@1.2 interface to create plugin");
880                     mICasHidl =
881                             mICasHidl11 =
882                                     mICasHidl12 =
883                                             android.hardware.cas.V1_2.ICas.castFrom(
884                                                     serviceV12.createPluginExt(
885                                                             casSystemId, mBinderHidl));
886                 }
887             }
888         } catch(Exception e) {
889             Log.e(TAG, "Failed to create plugin: " + e);
890             mICas = null;
891             mICasHidl = null;
892         } finally {
893             if (mICas == null && mICasHidl == null) {
894                 throw new UnsupportedCasException(
895                     "Unsupported casSystemId " + casSystemId);
896             }
897         }
898     }
899 
registerClient(@onNull Context context, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint)900     private void registerClient(@NonNull Context context,
901             @Nullable String tvInputServiceSessionId,  @PriorityHintUseCaseType int priorityHint)  {
902 
903         mTunerResourceManager = (TunerResourceManager)
904             context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
905         if (mTunerResourceManager != null) {
906             int[] clientId = new int[1];
907             ResourceClientProfile profile = new ResourceClientProfile();
908             profile.tvInputSessionId = tvInputServiceSessionId;
909             profile.useCase = priorityHint;
910             mTunerResourceManager.registerClientProfile(
911                     profile, context.getMainExecutor(), mResourceListener, clientId);
912             mClientId = clientId[0];
913         }
914     }
915     /**
916      * Instantiate a CA system of the specified system id.
917      *
918      * @param casSystemId The system id of the CA system.
919      *
920      * @throws UnsupportedCasException if the device does not support the
921      * specified CA system.
922      */
MediaCas(int casSystemId)923     public MediaCas(int casSystemId) throws UnsupportedCasException {
924         createPlugin(casSystemId);
925     }
926 
927     /**
928      * Instantiate a CA system of the specified system id.
929      *
930      * @param context the context of the caller.
931      * @param casSystemId The system id of the CA system.
932      * @param tvInputServiceSessionId The Id of the session opened in TV Input Service (TIS)
933      *        {@link android.media.tv.TvInputService#onCreateSession(String, String)}
934      * @param priorityHint priority hint from the use case type for new created CAS system.
935      *
936      * @throws UnsupportedCasException if the device does not support the
937      * specified CA system.
938      */
MediaCas(@onNull Context context, int casSystemId, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint)939     public MediaCas(@NonNull Context context, int casSystemId,
940             @Nullable String tvInputServiceSessionId,
941             @PriorityHintUseCaseType int priorityHint) throws UnsupportedCasException {
942         Objects.requireNonNull(context, "context must not be null");
943         createPlugin(casSystemId);
944         registerClient(context, tvInputServiceSessionId, priorityHint);
945     }
946     /**
947      * Instantiate a CA system of the specified system id with EvenListener.
948      *
949      * @param context the context of the caller.
950      * @param casSystemId The system id of the CA system.
951      * @param tvInputServiceSessionId The Id of the session opened in TV Input Service (TIS)
952      *        {@link android.media.tv.TvInputService#onCreateSession(String, String)}
953      * @param priorityHint priority hint from the use case type for new created CAS system.
954      * @param listener the event listener to be set.
955      * @param handler the handler whose looper the event listener will be called on.
956      * If handler is null, we'll try to use current thread's looper, or the main
957      * looper. If neither are available, an internal thread will be created instead.
958      *
959      * @throws UnsupportedCasException if the device does not support the
960      * specified CA system.
961      */
MediaCas(@onNull Context context, int casSystemId, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint, @Nullable Handler handler, @Nullable EventListener listener)962     public MediaCas(@NonNull Context context, int casSystemId,
963             @Nullable String tvInputServiceSessionId,
964             @PriorityHintUseCaseType int priorityHint,
965             @Nullable Handler handler, @Nullable EventListener listener)
966             throws UnsupportedCasException {
967         Objects.requireNonNull(context, "context must not be null");
968         setEventListener(listener, handler);
969         createPlugin(casSystemId);
970         registerClient(context, tvInputServiceSessionId, priorityHint);
971     }
972 
getBinder()973     IHwBinder getBinder() {
974         if (mICas != null) {
975             return null; // Return IHwBinder only for HIDL
976         }
977 
978         validateInternalStates();
979 
980         return mICasHidl.asBinder();
981     }
982 
983     /**
984      * Check if the HAL is an AIDL implementation. For CTS testing purpose.
985      *
986      * @hide
987      */
988     @TestApi
isAidlHal()989     public boolean isAidlHal() {
990         return mICas != null;
991     }
992 
993     /**
994      * An interface registered by the caller to {@link #setEventListener}
995      * to receives scheme-specific notifications from a MediaCas instance.
996      */
997     public interface EventListener {
998 
999         /**
1000          * Notify the listener of a scheme-specific event from the CA system.
1001          *
1002          * @param mediaCas the MediaCas object to receive this event.
1003          * @param event an integer whose meaning is scheme-specific.
1004          * @param arg an integer whose meaning is scheme-specific.
1005          * @param data a byte array of data whose format and meaning are
1006          * scheme-specific.
1007          */
onEvent(@onNull MediaCas mediaCas, int event, int arg, @Nullable byte[] data)1008         void onEvent(@NonNull MediaCas mediaCas, int event, int arg, @Nullable byte[] data);
1009 
1010         /**
1011          * Notify the listener of a scheme-specific session event from CA system.
1012          *
1013          * @param mediaCas the MediaCas object to receive this event.
1014          * @param session session object which the event is for.
1015          * @param event an integer whose meaning is scheme-specific.
1016          * @param arg an integer whose meaning is scheme-specific.
1017          * @param data a byte array of data whose format and meaning are
1018          * scheme-specific.
1019          */
onSessionEvent(@onNull MediaCas mediaCas, @NonNull Session session, int event, int arg, @Nullable byte[] data)1020         default void onSessionEvent(@NonNull MediaCas mediaCas, @NonNull Session session,
1021                 int event, int arg, @Nullable byte[] data) {
1022             Log.d(TAG, "Received MediaCas Session event");
1023         }
1024 
1025         /**
1026          * Notify the listener that the cas plugin status is updated.
1027          *
1028          * @param mediaCas the MediaCas object to receive this event.
1029          * @param status the plugin status which is updated.
1030          * @param arg an integer whose meaning is specific to the status to be updated.
1031          */
onPluginStatusUpdate(@onNull MediaCas mediaCas, @PluginStatus int status, int arg)1032         default void onPluginStatusUpdate(@NonNull MediaCas mediaCas, @PluginStatus int status,
1033                 int arg) {
1034             Log.d(TAG, "Received MediaCas Plugin Status event");
1035         }
1036 
1037         /**
1038          * Notify the listener that the session resources was lost.
1039          *
1040          * @param mediaCas the MediaCas object to receive this event.
1041          */
onResourceLost(@onNull MediaCas mediaCas)1042         default void onResourceLost(@NonNull MediaCas mediaCas) {
1043             Log.d(TAG, "Received MediaCas Resource Reclaim event");
1044         }
1045     }
1046 
1047     /**
1048      * Set an event listener to receive notifications from the MediaCas instance.
1049      *
1050      * @param listener the event listener to be set.
1051      * @param handler the handler whose looper the event listener will be called on.
1052      * If handler is null, we'll try to use current thread's looper, or the main
1053      * looper. If neither are available, an internal thread will be created instead.
1054      */
setEventListener( @ullable EventListener listener, @Nullable Handler handler)1055     public void setEventListener(
1056             @Nullable EventListener listener, @Nullable Handler handler) {
1057         mListener = listener;
1058 
1059         if (mListener == null) {
1060             mEventHandler = null;
1061             return;
1062         }
1063 
1064         Looper looper = (handler != null) ? handler.getLooper() : null;
1065         if (looper == null
1066                 && (looper = Looper.myLooper()) == null
1067                 && (looper = Looper.getMainLooper()) == null) {
1068             if (mHandlerThread == null || !mHandlerThread.isAlive()) {
1069                 mHandlerThread = new HandlerThread("MediaCasEventThread",
1070                         Process.THREAD_PRIORITY_FOREGROUND);
1071                 mHandlerThread.start();
1072             }
1073             looper = mHandlerThread.getLooper();
1074         }
1075         mEventHandler = new EventHandler(looper);
1076     }
1077 
1078     /**
1079      * Send the private data for the CA system.
1080      *
1081      * @param data byte array of the private data.
1082      *
1083      * @throws IllegalStateException if the MediaCas instance is not valid.
1084      * @throws MediaCasException for CAS-specific errors.
1085      * @throws MediaCasStateException for CAS-specific state exceptions.
1086      */
setPrivateData(@onNull byte[] data)1087     public void setPrivateData(@NonNull byte[] data) throws MediaCasException {
1088         validateInternalStates();
1089 
1090         try {
1091             if (mICas != null) {
1092                 try {
1093                     mICas.setPrivateData(data);
1094                 } catch (ServiceSpecificException se) {
1095                     MediaCasException.throwExceptionIfNeeded(se.errorCode);
1096                 }
1097             } else {
1098                 MediaCasException.throwExceptionIfNeeded(
1099                         mICasHidl.setPrivateData(toByteArray(data, 0, data.length)));
1100             }
1101         } catch (RemoteException e) {
1102             cleanupAndRethrowIllegalState();
1103         }
1104     }
1105 
1106     private class OpenSessionCallback implements android.hardware.cas.V1_1.ICas.openSessionCallback{
1107         public Session mSession;
1108         public int mStatus;
1109         @Override
onValues(int status, ArrayList<Byte> sessionId)1110         public void onValues(int status, ArrayList<Byte> sessionId) {
1111             mStatus = status;
1112             mSession = createFromSessionId(toBytes(sessionId));
1113         }
1114     }
1115 
1116     private class OpenSession_1_2_Callback implements
1117             android.hardware.cas.V1_2.ICas.openSession_1_2Callback {
1118 
1119         public Session mSession;
1120         public int mStatus;
1121 
1122         @Override
onValues(int status, ArrayList<Byte> sessionId)1123         public void onValues(int status, ArrayList<Byte> sessionId) {
1124             mStatus = status;
1125             mSession = createFromSessionId(toBytes(sessionId));
1126         }
1127     }
1128 
getSessionResourceHandle()1129     private int getSessionResourceHandle() throws MediaCasException {
1130         validateInternalStates();
1131 
1132         int[] sessionResourceHandle = new int[1];
1133         sessionResourceHandle[0] = -1;
1134         if (mTunerResourceManager != null) {
1135             CasSessionRequest casSessionRequest = new CasSessionRequest();
1136             casSessionRequest.clientId = mClientId;
1137             casSessionRequest.casSystemId = mCasSystemId;
1138             if (!mTunerResourceManager
1139                     .requestCasSession(casSessionRequest, sessionResourceHandle)) {
1140                 throw new MediaCasException.InsufficientResourceException(
1141                     "insufficient resource to Open Session");
1142             }
1143         }
1144         return sessionResourceHandle[0];
1145     }
1146 
addSessionToResourceMap(Session session, int sessionResourceHandle)1147     private void addSessionToResourceMap(Session session, int sessionResourceHandle) {
1148 
1149         if (sessionResourceHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
1150             synchronized (mSessionMap) {
1151                 mSessionMap.put(session, sessionResourceHandle);
1152             }
1153         }
1154     }
1155 
removeSessionFromResourceMap(Session session)1156     private void removeSessionFromResourceMap(Session session) {
1157 
1158         synchronized (mSessionMap) {
1159             if (mSessionMap.get(session) != null) {
1160                 mTunerResourceManager.releaseCasSession(mSessionMap.get(session), mClientId);
1161                 mSessionMap.remove(session);
1162             }
1163         }
1164     }
1165 
1166     /**
1167      * Open a session to descramble one or more streams scrambled by the
1168      * conditional access system.
1169      *
1170      * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
1171      * to get cas session resource if cas session resources is limited. If the client can't get the
1172      * resource, this call returns {@link MediaCasException.InsufficientResourceException }.
1173      *
1174      * @return session the newly opened session.
1175      *
1176      * @throws IllegalStateException if the MediaCas instance is not valid.
1177      * @throws MediaCasException for CAS-specific errors.
1178      * @throws MediaCasStateException for CAS-specific state exceptions.
1179      */
openSession()1180     public Session openSession() throws MediaCasException {
1181         int sessionResourceHandle = getSessionResourceHandle();
1182 
1183         try {
1184             if (mICas != null) {
1185                 try {
1186                     byte[] sessionId = mICas.openSessionDefault();
1187                     Session session = createFromSessionId(sessionId);
1188                     Log.d(TAG, "Write Stats Log for succeed to Open Session.");
1189                     FrameworkStatsLog.write(
1190                             FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS,
1191                             mUserId,
1192                             mCasSystemId,
1193                             FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED);
1194                     return session;
1195                 } catch (ServiceSpecificException se) {
1196                     MediaCasException.throwExceptionIfNeeded(se.errorCode);
1197                 }
1198             } else if (mICasHidl != null) {
1199                 OpenSessionCallback cb = new OpenSessionCallback();
1200                 mICasHidl.openSession(cb);
1201                 MediaCasException.throwExceptionIfNeeded(cb.mStatus);
1202                 addSessionToResourceMap(cb.mSession, sessionResourceHandle);
1203                 Log.d(TAG, "Write Stats Log for succeed to Open Session.");
1204                 FrameworkStatsLog.write(
1205                         FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS,
1206                         mUserId,
1207                         mCasSystemId,
1208                         FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED);
1209                 return cb.mSession;
1210             }
1211         } catch (RemoteException e) {
1212             cleanupAndRethrowIllegalState();
1213         }
1214         Log.d(TAG, "Write Stats Log for fail to Open Session.");
1215         FrameworkStatsLog
1216                 .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId,
1217                     FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED);
1218         return null;
1219     }
1220 
1221     /**
1222      * Open a session with usage and scrambling information, so that descrambler can be configured
1223      * to descramble one or more streams scrambled by the conditional access system.
1224      *
1225      * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
1226      * to get cas session resource if cas session resources is limited. If the client can't get the
1227      * resource, this call returns {@link MediaCasException.InsufficientResourceException}.
1228      *
1229      * @param sessionUsage used for the created session.
1230      * @param scramblingMode used for the created session.
1231      *
1232      * @return session the newly opened session.
1233      *
1234      * @throws IllegalStateException if the MediaCas instance is not valid.
1235      * @throws MediaCasException for CAS-specific errors.
1236      * @throws MediaCasStateException for CAS-specific state exceptions.
1237      */
1238     @Nullable
openSession(@essionUsage int sessionUsage, @ScramblingMode int scramblingMode)1239     public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode)
1240             throws MediaCasException {
1241         int sessionResourceHandle = getSessionResourceHandle();
1242 
1243         if (mICas != null) {
1244             try {
1245                 byte[] sessionId = mICas.openSession(sessionUsage, scramblingMode);
1246                 Session session = createFromSessionId(sessionId);
1247                 addSessionToResourceMap(session, sessionResourceHandle);
1248                 Log.d(TAG, "Write Stats Log for succeed to Open Session.");
1249                 FrameworkStatsLog.write(
1250                         FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS,
1251                         mUserId,
1252                         mCasSystemId,
1253                         FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED);
1254                 return session;
1255             } catch (ServiceSpecificException | RemoteException e) {
1256                 cleanupAndRethrowIllegalState();
1257             }
1258         }
1259         if (mICasHidl12 == null) {
1260             Log.d(TAG, "Open Session with scrambling mode is only supported by cas@1.2+ interface");
1261             throw new UnsupportedCasException("Open Session with scrambling mode is not supported");
1262         }
1263 
1264         try {
1265             OpenSession_1_2_Callback cb = new OpenSession_1_2_Callback();
1266             mICasHidl12.openSession_1_2(sessionUsage, scramblingMode, cb);
1267             MediaCasException.throwExceptionIfNeeded(cb.mStatus);
1268             addSessionToResourceMap(cb.mSession, sessionResourceHandle);
1269             Log.d(TAG, "Write Stats Log for succeed to Open Session.");
1270             FrameworkStatsLog
1271                     .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId,
1272                         FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED);
1273             return cb.mSession;
1274         } catch (RemoteException e) {
1275             cleanupAndRethrowIllegalState();
1276         }
1277         Log.d(TAG, "Write Stats Log for fail to Open Session.");
1278         FrameworkStatsLog
1279                 .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId,
1280                     FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED);
1281         return null;
1282     }
1283 
1284     /**
1285      * Send a received EMM packet to the CA system.
1286      *
1287      * @param data byte array of the EMM data.
1288      * @param offset position within data where the EMM data begins.
1289      * @param length length of the data (starting from offset).
1290      *
1291      * @throws IllegalStateException if the MediaCas instance is not valid.
1292      * @throws MediaCasException for CAS-specific errors.
1293      * @throws MediaCasStateException for CAS-specific state exceptions.
1294      */
processEmm(@onNull byte[] data, int offset, int length)1295     public void processEmm(@NonNull byte[] data, int offset, int length)
1296             throws MediaCasException {
1297         validateInternalStates();
1298 
1299         try {
1300             if (mICas != null) {
1301                 try {
1302                     mICas.processEmm(Arrays.copyOfRange(data, offset, length));
1303                 } catch (ServiceSpecificException se) {
1304                     MediaCasException.throwExceptionIfNeeded(se.errorCode);
1305                 }
1306             } else {
1307                 MediaCasException.throwExceptionIfNeeded(
1308                         mICasHidl.processEmm(toByteArray(data, offset, length)));
1309             }
1310         } catch (RemoteException e) {
1311             cleanupAndRethrowIllegalState();
1312         }
1313     }
1314 
1315     /**
1316      * Send a received EMM packet to the CA system. This is similar to
1317      * {@link #processEmm(byte[], int, int)} except that the entire byte
1318      * array is sent.
1319      *
1320      * @param data byte array of the EMM data.
1321      *
1322      * @throws IllegalStateException if the MediaCas instance is not valid.
1323      * @throws MediaCasException for CAS-specific errors.
1324      * @throws MediaCasStateException for CAS-specific state exceptions.
1325      */
processEmm(@onNull byte[] data)1326     public void processEmm(@NonNull byte[] data) throws MediaCasException {
1327         processEmm(data, 0, data.length);
1328     }
1329 
1330     /**
1331      * Send an event to a CA system. The format of the event is scheme-specific
1332      * and is opaque to the framework.
1333      *
1334      * @param event an integer denoting a scheme-specific event to be sent.
1335      * @param arg a scheme-specific integer argument for the event.
1336      * @param data a byte array containing scheme-specific data for the event.
1337      *
1338      * @throws IllegalStateException if the MediaCas instance is not valid.
1339      * @throws MediaCasException for CAS-specific errors.
1340      * @throws MediaCasStateException for CAS-specific state exceptions.
1341      */
sendEvent(int event, int arg, @Nullable byte[] data)1342     public void sendEvent(int event, int arg, @Nullable byte[] data)
1343             throws MediaCasException {
1344         validateInternalStates();
1345 
1346         try {
1347             if (mICas != null) {
1348                 try {
1349                     if (data == null) {
1350                         data = new byte[0];
1351                     }
1352                     mICas.sendEvent(event, arg, data);
1353                 } catch (ServiceSpecificException se) {
1354                     MediaCasException.throwExceptionIfNeeded(se.errorCode);
1355                 }
1356             } else {
1357                 MediaCasException.throwExceptionIfNeeded(
1358                         mICasHidl.sendEvent(event, arg, toByteArray(data)));
1359             }
1360         } catch (RemoteException e) {
1361             cleanupAndRethrowIllegalState();
1362         }
1363     }
1364 
1365    /**
1366      * Initiate a provisioning operation for a CA system.
1367      *
1368      * @param provisionString string containing information needed for the
1369      * provisioning operation, the format of which is scheme and implementation
1370      * specific.
1371      *
1372      * @throws IllegalStateException if the MediaCas instance is not valid.
1373      * @throws MediaCasException for CAS-specific errors.
1374      * @throws MediaCasStateException for CAS-specific state exceptions.
1375      */
provision(@onNull String provisionString)1376     public void provision(@NonNull String provisionString) throws MediaCasException {
1377         validateInternalStates();
1378 
1379         try {
1380             if (mICas != null) {
1381                 try {
1382                     mICas.provision(provisionString);
1383                 } catch (ServiceSpecificException se) {
1384                     MediaCasException.throwExceptionIfNeeded(se.errorCode);
1385                 }
1386             } else {
1387                 MediaCasException.throwExceptionIfNeeded(mICasHidl.provision(provisionString));
1388             }
1389         } catch (RemoteException e) {
1390             cleanupAndRethrowIllegalState();
1391         }
1392     }
1393 
1394     /**
1395      * Notify the CA system to refresh entitlement keys.
1396      *
1397      * @param refreshType the type of the refreshment.
1398      * @param refreshData private data associated with the refreshment.
1399      *
1400      * @throws IllegalStateException if the MediaCas instance is not valid.
1401      * @throws MediaCasException for CAS-specific errors.
1402      * @throws MediaCasStateException for CAS-specific state exceptions.
1403      */
refreshEntitlements(int refreshType, @Nullable byte[] refreshData)1404     public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData)
1405             throws MediaCasException {
1406         validateInternalStates();
1407 
1408         try {
1409             if (mICas != null) {
1410                 try {
1411                     if (refreshData == null) {
1412                         refreshData = new byte[0];
1413                     }
1414                     mICas.refreshEntitlements(refreshType, refreshData);
1415                 } catch (ServiceSpecificException se) {
1416                     MediaCasException.throwExceptionIfNeeded(se.errorCode);
1417                 }
1418             } else {
1419                 MediaCasException.throwExceptionIfNeeded(
1420                         mICasHidl.refreshEntitlements(refreshType, toByteArray(refreshData)));
1421             }
1422         } catch (RemoteException e) {
1423             cleanupAndRethrowIllegalState();
1424         }
1425     }
1426 
1427     /**
1428      * Release Cas session. This is primarily used as a test API for CTS.
1429      * @hide
1430      */
1431     @TestApi
forceResourceLost()1432     public void forceResourceLost() {
1433         if (mResourceListener != null) {
1434             mResourceListener.onReclaimResources();
1435         }
1436     }
1437 
1438     @Override
close()1439     public void close() {
1440         if (mICas != null) {
1441             try {
1442                 mICas.release();
1443             } catch (RemoteException e) {
1444             } finally {
1445                 mICas = null;
1446             }
1447         } else if (mICasHidl != null) {
1448             try {
1449                 mICasHidl.release();
1450             } catch (RemoteException e) {
1451             } finally {
1452                 mICasHidl = mICasHidl11 = mICasHidl12 = null;
1453             }
1454         }
1455 
1456         if (mTunerResourceManager != null) {
1457             mTunerResourceManager.unregisterClientProfile(mClientId);
1458             mTunerResourceManager = null;
1459         }
1460 
1461         if (mHandlerThread != null) {
1462             mHandlerThread.quit();
1463             mHandlerThread = null;
1464         }
1465     }
1466 
1467     @Override
finalize()1468     protected void finalize() {
1469         close();
1470     }
1471 }
1472