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