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