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.NonNull;
20 import android.annotation.Nullable;
21 import android.hardware.cas.V1_0.HidlCasPluginDescriptor;
22 import android.hardware.cas.V1_0.ICas;
23 import android.hardware.cas.V1_0.IMediaCasService;
24 import android.hardware.cas.V1_1.ICasListener;
25 import android.media.MediaCasException.*;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.HandlerThread;
29 import android.os.IHwBinder;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.os.Process;
33 import android.os.RemoteException;
34 import android.util.Log;
35 import android.util.Singleton;
36 
37 import java.util.ArrayList;
38 
39 /**
40  * MediaCas can be used to obtain keys for descrambling protected media streams, in
41  * conjunction with {@link android.media.MediaDescrambler}. The MediaCas APIs are
42  * designed to support conditional access such as those in the ISO/IEC13818-1.
43  * The CA system is identified by a 16-bit integer CA_system_id. The scrambling
44  * algorithms are usually proprietary and implemented by vendor-specific CA plugins
45  * installed on the device.
46  * <p>
47  * The app is responsible for constructing a MediaCas object for the CA system it
48  * intends to use. The app can query if a certain CA system is supported using static
49  * method {@link #isSystemIdSupported}. It can also obtain the entire list of supported
50  * CA systems using static method {@link #enumeratePlugins}.
51  * <p>
52  * Once the MediaCas object is constructed, the app should properly provision it by
53  * using method {@link #provision} and/or {@link #processEmm}. The EMMs (Entitlement
54  * management messages) can be distributed out-of-band, or in-band with the stream.
55  * <p>
56  * To descramble elementary streams, the app first calls {@link #openSession} to
57  * generate a {@link Session} object that will uniquely identify a session. A session
58  * provides a context for subsequent key updates and descrambling activities. The ECMs
59  * (Entitlement control messages) are sent to the session via method
60  * {@link Session#processEcm}.
61  * <p>
62  * The app next constructs a MediaDescrambler object, and initializes it with the
63  * session using {@link MediaDescrambler#setMediaCasSession}. This ties the
64  * descrambler to the session, and the descrambler can then be used to descramble
65  * content secured with the session's key, either during extraction, or during decoding
66  * with {@link android.media.MediaCodec}.
67  * <p>
68  * If the app handles sample extraction using its own extractor, it can use
69  * MediaDescrambler to descramble samples into clear buffers (if the session's license
70  * doesn't require secure decoders), or descramble a small amount of data to retrieve
71  * information necessary for the downstream pipeline to process the sample (if the
72  * session's license requires secure decoders).
73  * <p>
74  * If the session requires a secure decoder, a MediaDescrambler needs to be provided to
75  * MediaCodec to descramble samples queued by {@link MediaCodec#queueSecureInputBuffer}
76  * into protected buffers. The app should use {@link MediaCodec#configure(MediaFormat,
77  * android.view.Surface, int, MediaDescrambler)} instead of the normal {@link
78  * MediaCodec#configure(MediaFormat, android.view.Surface, MediaCrypto, int)} method
79  * to configure MediaCodec.
80  * <p>
81  * <h3>Using Android's MediaExtractor</h3>
82  * <p>
83  * If the app uses {@link MediaExtractor}, it can delegate the CAS session
84  * management to MediaExtractor by calling {@link MediaExtractor#setMediaCas}.
85  * MediaExtractor will take over and call {@link #openSession}, {@link #processEmm}
86  * and/or {@link Session#processEcm}, etc.. if necessary.
87  * <p>
88  * When using {@link MediaExtractor}, the app would still need a MediaDescrambler
89  * to use with {@link MediaCodec} if the licensing requires a secure decoder. The
90  * session associated with the descrambler of a track can be retrieved by calling
91  * {@link MediaExtractor#getCasInfo}, and used to initialize a MediaDescrambler
92  * object for MediaCodec.
93  * <p>
94  * <h3>Listeners</h3>
95  * <p>The app may register a listener to receive events from the CA system using
96  * method {@link #setEventListener}. The exact format of the event is scheme-specific
97  * and is not specified by this API.
98  */
99 public final class MediaCas implements AutoCloseable {
100     private static final String TAG = "MediaCas";
101     private ICas mICas;
102     private android.hardware.cas.V1_1.ICas mICasV11;
103     private EventListener mListener;
104     private HandlerThread mHandlerThread;
105     private EventHandler mEventHandler;
106 
107     private static final Singleton<IMediaCasService> sService = new Singleton<IMediaCasService>() {
108         @Override
109         protected IMediaCasService create() {
110             try {
111                 Log.d(TAG, "Tried to get cas@1.1 service");
112                 android.hardware.cas.V1_1.IMediaCasService serviceV11 =
113                         android.hardware.cas.V1_1.IMediaCasService.getService(true /*wait*/);
114                 if (serviceV11 != null) {
115                     return serviceV11;
116                 }
117             } catch (Exception eV1_1) {
118                 try {
119                     Log.d(TAG, "Tried to get cas@1.0 service");
120                     return IMediaCasService.getService(true /*wait*/);
121                 } catch (Exception eV1_0) {
122                     Log.d(TAG, "Failed to get cas@1.0 service");
123                 }
124             }
125             return null;
126         }
127     };
128 
getService()129     static IMediaCasService getService() {
130         return sService.get();
131     }
132 
validateInternalStates()133     private void validateInternalStates() {
134         if (mICas == null) {
135             throw new IllegalStateException();
136         }
137     }
138 
cleanupAndRethrowIllegalState()139     private void cleanupAndRethrowIllegalState() {
140         mICas = null;
141         mICasV11 = null;
142         throw new IllegalStateException();
143     }
144 
145     private class EventHandler extends Handler {
146 
147         private static final int MSG_CAS_EVENT = 0;
148         private static final int MSG_CAS_SESSION_EVENT = 1;
149         private static final String SESSION_KEY = "sessionId";
150         private static final String DATA_KEY = "data";
151 
EventHandler(Looper looper)152         public EventHandler(Looper looper) {
153             super(looper);
154         }
155 
156         @Override
handleMessage(Message msg)157         public void handleMessage(Message msg) {
158             if (msg.what == MSG_CAS_EVENT) {
159                 mListener.onEvent(MediaCas.this, msg.arg1, msg.arg2,
160                         toBytes((ArrayList<Byte>) msg.obj));
161             } else if (msg.what == MSG_CAS_SESSION_EVENT) {
162                 Bundle bundle = msg.getData();
163                 ArrayList<Byte> sessionId = toByteArray(bundle.getByteArray(SESSION_KEY));
164                 mListener.onSessionEvent(MediaCas.this,
165                         createFromSessionId(sessionId), msg.arg1, msg.arg2,
166                         bundle.getByteArray(DATA_KEY));
167             }
168         }
169     }
170 
171     private final ICasListener.Stub mBinder = new ICasListener.Stub() {
172         @Override
173         public void onEvent(int event, int arg, @Nullable ArrayList<Byte> data)
174                 throws RemoteException {
175             mEventHandler.sendMessage(mEventHandler.obtainMessage(
176                     EventHandler.MSG_CAS_EVENT, event, arg, data));
177         }
178         @Override
179         public void onSessionEvent(@NonNull ArrayList<Byte> sessionId,
180                 int event, int arg, @Nullable ArrayList<Byte> data)
181                 throws RemoteException {
182             Message msg = mEventHandler.obtainMessage();
183             msg.what = EventHandler.MSG_CAS_SESSION_EVENT;
184             msg.arg1 = event;
185             msg.arg2 = arg;
186             Bundle bundle = new Bundle();
187             bundle.putByteArray(EventHandler.SESSION_KEY, toBytes(sessionId));
188             bundle.putByteArray(EventHandler.DATA_KEY, toBytes(data));
189             msg.setData(bundle);
190             mEventHandler.sendMessage(msg);
191         }
192     };
193     /**
194      * Describe a CAS plugin with its CA_system_ID and string name.
195      *
196      * Returned as results of {@link #enumeratePlugins}.
197      *
198      */
199     public static class PluginDescriptor {
200         private final int mCASystemId;
201         private final String mName;
202 
PluginDescriptor()203         private PluginDescriptor() {
204             mCASystemId = 0xffff;
205             mName = null;
206         }
207 
PluginDescriptor(@onNull HidlCasPluginDescriptor descriptor)208         PluginDescriptor(@NonNull HidlCasPluginDescriptor descriptor) {
209             mCASystemId = descriptor.caSystemId;
210             mName = descriptor.name;
211         }
212 
getSystemId()213         public int getSystemId() {
214             return mCASystemId;
215         }
216 
217         @NonNull
getName()218         public String getName() {
219             return mName;
220         }
221 
222         @Override
toString()223         public String toString() {
224             return "PluginDescriptor {" + mCASystemId + ", " + mName + "}";
225         }
226     }
227 
toByteArray(@onNull byte[] data, int offset, int length)228     private ArrayList<Byte> toByteArray(@NonNull byte[] data, int offset, int length) {
229         ArrayList<Byte> byteArray = new ArrayList<Byte>(length);
230         for (int i = 0; i < length; i++) {
231             byteArray.add(Byte.valueOf(data[offset + i]));
232         }
233         return byteArray;
234     }
235 
toByteArray(@ullable byte[] data)236     private ArrayList<Byte> toByteArray(@Nullable byte[] data) {
237         if (data == null) {
238             return new ArrayList<Byte>();
239         }
240         return toByteArray(data, 0, data.length);
241     }
242 
toBytes(@onNull ArrayList<Byte> byteArray)243     private byte[] toBytes(@NonNull ArrayList<Byte> byteArray) {
244         byte[] data = null;
245         if (byteArray != null) {
246             data = new byte[byteArray.size()];
247             for (int i = 0; i < data.length; i++) {
248                 data[i] = byteArray.get(i);
249             }
250         }
251         return data;
252     }
253     /**
254      * Class for an open session with the CA system.
255      */
256     public final class Session implements AutoCloseable {
257         final ArrayList<Byte> mSessionId;
258 
Session(@onNull ArrayList<Byte> sessionId)259         Session(@NonNull ArrayList<Byte> sessionId) {
260             mSessionId = sessionId;
261         }
262 
263         /**
264          * Query if an object equal current Session object.
265          *
266          * @param obj an object to compare to current Session object.
267          *
268          * @return Whether input object equal current Session object.
269          */
equals(Object obj)270         public boolean equals(Object obj) {
271             if (obj instanceof Session) {
272                 return mSessionId.equals(((Session) obj).mSessionId);
273             }
274             return false;
275         }
276 
277         /**
278          * Set the private data for a session.
279          *
280          * @param data byte array of the private data.
281          *
282          * @throws IllegalStateException if the MediaCas instance is not valid.
283          * @throws MediaCasException for CAS-specific errors.
284          * @throws MediaCasStateException for CAS-specific state exceptions.
285          */
setPrivateData(@onNull byte[] data)286         public void setPrivateData(@NonNull byte[] data)
287                 throws MediaCasException {
288             validateInternalStates();
289 
290             try {
291                 MediaCasException.throwExceptionIfNeeded(
292                         mICas.setSessionPrivateData(mSessionId, toByteArray(data, 0, data.length)));
293             } catch (RemoteException e) {
294                 cleanupAndRethrowIllegalState();
295             }
296         }
297 
298 
299         /**
300          * Send a received ECM packet to the specified session of the CA system.
301          *
302          * @param data byte array of the ECM data.
303          * @param offset position within data where the ECM data begins.
304          * @param length length of the data (starting from offset).
305          *
306          * @throws IllegalStateException if the MediaCas instance is not valid.
307          * @throws MediaCasException for CAS-specific errors.
308          * @throws MediaCasStateException for CAS-specific state exceptions.
309          */
processEcm(@onNull byte[] data, int offset, int length)310         public void processEcm(@NonNull byte[] data, int offset, int length)
311                 throws MediaCasException {
312             validateInternalStates();
313 
314             try {
315                 MediaCasException.throwExceptionIfNeeded(
316                         mICas.processEcm(mSessionId, toByteArray(data, offset, length)));
317             } catch (RemoteException e) {
318                 cleanupAndRethrowIllegalState();
319             }
320         }
321 
322         /**
323          * Send a received ECM packet to the specified session of the CA system.
324          * This is similar to {@link Session#processEcm(byte[], int, int)}
325          * except that the entire byte array is sent.
326          *
327          * @param data byte array of the ECM data.
328          *
329          * @throws IllegalStateException if the MediaCas instance is not valid.
330          * @throws MediaCasException for CAS-specific errors.
331          * @throws MediaCasStateException for CAS-specific state exceptions.
332          */
processEcm(@onNull byte[] data)333         public void processEcm(@NonNull byte[] data) throws MediaCasException {
334             processEcm(data, 0, data.length);
335         }
336 
337         /**
338          * Send a session event to a CA system. The format of the event is
339          * scheme-specific and is opaque to the framework.
340          *
341          * @param event an integer denoting a scheme-specific event to be sent.
342          * @param arg a scheme-specific integer argument for the event.
343          * @param data a byte array containing scheme-specific data for the event.
344          *
345          * @throws IllegalStateException if the MediaCas instance is not valid.
346          * @throws MediaCasException for CAS-specific errors.
347          * @throws MediaCasStateException for CAS-specific state exceptions.
348          */
sendSessionEvent(int event, int arg, @Nullable byte[] data)349         public void sendSessionEvent(int event, int arg, @Nullable byte[] data)
350                 throws MediaCasException {
351             validateInternalStates();
352 
353             if (mICasV11 == null) {
354                 Log.d(TAG, "Send Session Event isn't supported by cas@1.0 interface");
355                 throw new UnsupportedCasException("Send Session Event is not supported");
356             }
357 
358             try {
359                 MediaCasException.throwExceptionIfNeeded(
360                         mICasV11.sendSessionEvent(mSessionId, event, arg, toByteArray(data)));
361             } catch (RemoteException e) {
362                 cleanupAndRethrowIllegalState();
363             }
364         }
365 
366         /**
367          * Close the session.
368          *
369          * @throws IllegalStateException if the MediaCas instance is not valid.
370          * @throws MediaCasStateException for CAS-specific state exceptions.
371          */
372         @Override
close()373         public void close() {
374             validateInternalStates();
375 
376             try {
377                 MediaCasStateException.throwExceptionIfNeeded(
378                         mICas.closeSession(mSessionId));
379             } catch (RemoteException e) {
380                 cleanupAndRethrowIllegalState();
381             }
382         }
383     }
384 
createFromSessionId(@onNull ArrayList<Byte> sessionId)385     Session createFromSessionId(@NonNull ArrayList<Byte> sessionId) {
386         if (sessionId == null || sessionId.size() == 0) {
387             return null;
388         }
389         return new Session(sessionId);
390     }
391 
392     /**
393      * Query if a certain CA system is supported on this device.
394      *
395      * @param CA_system_id the id of the CA system.
396      *
397      * @return Whether the specified CA system is supported on this device.
398      */
isSystemIdSupported(int CA_system_id)399     public static boolean isSystemIdSupported(int CA_system_id) {
400         IMediaCasService service = getService();
401 
402         if (service != null) {
403             try {
404                 return service.isSystemIdSupported(CA_system_id);
405             } catch (RemoteException e) {
406             }
407         }
408         return false;
409     }
410 
411     /**
412      * List all available CA plugins on the device.
413      *
414      * @return an array of descriptors for the available CA plugins.
415      */
enumeratePlugins()416     public static PluginDescriptor[] enumeratePlugins() {
417         IMediaCasService service = getService();
418 
419         if (service != null) {
420             try {
421                 ArrayList<HidlCasPluginDescriptor> descriptors =
422                         service.enumeratePlugins();
423                 if (descriptors.size() == 0) {
424                     return null;
425                 }
426                 PluginDescriptor[] results = new PluginDescriptor[descriptors.size()];
427                 for (int i = 0; i < results.length; i++) {
428                     results[i] = new PluginDescriptor(descriptors.get(i));
429                 }
430                 return results;
431             } catch (RemoteException e) {
432             }
433         }
434         return null;
435     }
436 
437     /**
438      * Instantiate a CA system of the specified system id.
439      *
440      * @param CA_system_id The system id of the CA system.
441      *
442      * @throws UnsupportedCasException if the device does not support the
443      * specified CA system.
444      */
MediaCas(int CA_system_id)445     public MediaCas(int CA_system_id) throws UnsupportedCasException {
446         try {
447             IMediaCasService service = getService();
448             android.hardware.cas.V1_1.IMediaCasService serviceV11 =
449                     android.hardware.cas.V1_1.IMediaCasService.castFrom(service);
450             if (serviceV11 == null) {
451                 Log.d(TAG, "Used cas@1_0 interface to create plugin");
452                 mICas = service.createPlugin(CA_system_id, mBinder);
453             } else {
454                 Log.d(TAG, "Used cas@1.1 interface to create plugin");
455                 mICas = mICasV11 = serviceV11.createPluginExt(CA_system_id, mBinder);
456             }
457         } catch(Exception e) {
458             Log.e(TAG, "Failed to create plugin: " + e);
459             mICas = null;
460         } finally {
461             if (mICas == null) {
462                 throw new UnsupportedCasException(
463                         "Unsupported CA_system_id " + CA_system_id);
464             }
465         }
466     }
467 
getBinder()468     IHwBinder getBinder() {
469         validateInternalStates();
470 
471         return mICas.asBinder();
472     }
473 
474     /**
475      * An interface registered by the caller to {@link #setEventListener}
476      * to receives scheme-specific notifications from a MediaCas instance.
477      */
478     public interface EventListener {
479         /**
480          * Notify the listener of a scheme-specific event from the CA system.
481          *
482          * @param mediaCas the MediaCas object to receive this event.
483          * @param event an integer whose meaning is scheme-specific.
484          * @param arg an integer whose meaning is scheme-specific.
485          * @param data a byte array of data whose format and meaning are
486          * scheme-specific.
487          */
onEvent(@onNull MediaCas mediaCas, int event, int arg, @Nullable byte[] data)488         void onEvent(@NonNull MediaCas mediaCas, int event, int arg, @Nullable byte[] data);
489 
490         /**
491          * Notify the listener of a scheme-specific session event from CA system.
492          *
493          * @param mediaCas the MediaCas object to receive this event.
494          * @param session session object which the event is for.
495          * @param event an integer whose meaning is scheme-specific.
496          * @param arg an integer whose meaning is scheme-specific.
497          * @param data a byte array of data whose format and meaning are
498          * scheme-specific.
499          */
onSessionEvent(@onNull MediaCas mediaCas, @NonNull Session session, int event, int arg, @Nullable byte[] data)500         default void onSessionEvent(@NonNull MediaCas mediaCas, @NonNull Session session,
501                 int event, int arg, @Nullable byte[] data) {
502             Log.d(TAG, "Received MediaCas Session event");
503         }
504     }
505 
506     /**
507      * Set an event listener to receive notifications from the MediaCas instance.
508      *
509      * @param listener the event listener to be set.
510      * @param handler the handler whose looper the event listener will be called on.
511      * If handler is null, we'll try to use current thread's looper, or the main
512      * looper. If neither are available, an internal thread will be created instead.
513      */
setEventListener( @ullable EventListener listener, @Nullable Handler handler)514     public void setEventListener(
515             @Nullable EventListener listener, @Nullable Handler handler) {
516         mListener = listener;
517 
518         if (mListener == null) {
519             mEventHandler = null;
520             return;
521         }
522 
523         Looper looper = (handler != null) ? handler.getLooper() : null;
524         if (looper == null
525                 && (looper = Looper.myLooper()) == null
526                 && (looper = Looper.getMainLooper()) == null) {
527             if (mHandlerThread == null || !mHandlerThread.isAlive()) {
528                 mHandlerThread = new HandlerThread("MediaCasEventThread",
529                         Process.THREAD_PRIORITY_FOREGROUND);
530                 mHandlerThread.start();
531             }
532             looper = mHandlerThread.getLooper();
533         }
534         mEventHandler = new EventHandler(looper);
535     }
536 
537     /**
538      * Send the private data for the CA system.
539      *
540      * @param data byte array of the private data.
541      *
542      * @throws IllegalStateException if the MediaCas instance is not valid.
543      * @throws MediaCasException for CAS-specific errors.
544      * @throws MediaCasStateException for CAS-specific state exceptions.
545      */
setPrivateData(@onNull byte[] data)546     public void setPrivateData(@NonNull byte[] data) throws MediaCasException {
547         validateInternalStates();
548 
549         try {
550             MediaCasException.throwExceptionIfNeeded(
551                     mICas.setPrivateData(toByteArray(data, 0, data.length)));
552         } catch (RemoteException e) {
553             cleanupAndRethrowIllegalState();
554         }
555     }
556 
557     private class OpenSessionCallback implements android.hardware.cas.V1_1.ICas.openSessionCallback{
558         public Session mSession;
559         public int mStatus;
560         @Override
onValues(int status, ArrayList<Byte> sessionId)561         public void onValues(int status, ArrayList<Byte> sessionId) {
562             mStatus = status;
563             mSession = createFromSessionId(sessionId);
564         }
565     }
566     /**
567      * Open a session to descramble one or more streams scrambled by the
568      * conditional access system.
569      *
570      * @return session the newly opened session.
571      *
572      * @throws IllegalStateException if the MediaCas instance is not valid.
573      * @throws MediaCasException for CAS-specific errors.
574      * @throws MediaCasStateException for CAS-specific state exceptions.
575      */
openSession()576     public Session openSession() throws MediaCasException {
577         validateInternalStates();
578 
579         try {
580             OpenSessionCallback cb = new OpenSessionCallback();
581             mICas.openSession(cb);
582             MediaCasException.throwExceptionIfNeeded(cb.mStatus);
583             return cb.mSession;
584         } catch (RemoteException e) {
585             cleanupAndRethrowIllegalState();
586         }
587         return null;
588     }
589 
590     /**
591      * Send a received EMM packet to the CA system.
592      *
593      * @param data byte array of the EMM data.
594      * @param offset position within data where the EMM data begins.
595      * @param length length of the data (starting from offset).
596      *
597      * @throws IllegalStateException if the MediaCas instance is not valid.
598      * @throws MediaCasException for CAS-specific errors.
599      * @throws MediaCasStateException for CAS-specific state exceptions.
600      */
processEmm(@onNull byte[] data, int offset, int length)601     public void processEmm(@NonNull byte[] data, int offset, int length)
602             throws MediaCasException {
603         validateInternalStates();
604 
605         try {
606             MediaCasException.throwExceptionIfNeeded(
607                     mICas.processEmm(toByteArray(data, offset, length)));
608         } catch (RemoteException e) {
609             cleanupAndRethrowIllegalState();
610         }
611     }
612 
613     /**
614      * Send a received EMM packet to the CA system. This is similar to
615      * {@link #processEmm(byte[], int, int)} except that the entire byte
616      * array is sent.
617      *
618      * @param data byte array of the EMM data.
619      *
620      * @throws IllegalStateException if the MediaCas instance is not valid.
621      * @throws MediaCasException for CAS-specific errors.
622      * @throws MediaCasStateException for CAS-specific state exceptions.
623      */
processEmm(@onNull byte[] data)624     public void processEmm(@NonNull byte[] data) throws MediaCasException {
625         processEmm(data, 0, data.length);
626     }
627 
628     /**
629      * Send an event to a CA system. The format of the event is scheme-specific
630      * and is opaque to the framework.
631      *
632      * @param event an integer denoting a scheme-specific event to be sent.
633      * @param arg a scheme-specific integer argument for the event.
634      * @param data a byte array containing scheme-specific data for the event.
635      *
636      * @throws IllegalStateException if the MediaCas instance is not valid.
637      * @throws MediaCasException for CAS-specific errors.
638      * @throws MediaCasStateException for CAS-specific state exceptions.
639      */
sendEvent(int event, int arg, @Nullable byte[] data)640     public void sendEvent(int event, int arg, @Nullable byte[] data)
641             throws MediaCasException {
642         validateInternalStates();
643 
644         try {
645             MediaCasException.throwExceptionIfNeeded(
646                     mICas.sendEvent(event, arg, toByteArray(data)));
647         } catch (RemoteException e) {
648             cleanupAndRethrowIllegalState();
649         }
650     }
651 
652    /**
653      * Initiate a provisioning operation for a CA system.
654      *
655      * @param provisionString string containing information needed for the
656      * provisioning operation, the format of which is scheme and implementation
657      * specific.
658      *
659      * @throws IllegalStateException if the MediaCas instance is not valid.
660      * @throws MediaCasException for CAS-specific errors.
661      * @throws MediaCasStateException for CAS-specific state exceptions.
662      */
provision(@onNull String provisionString)663     public void provision(@NonNull String provisionString) throws MediaCasException {
664         validateInternalStates();
665 
666         try {
667             MediaCasException.throwExceptionIfNeeded(
668                     mICas.provision(provisionString));
669         } catch (RemoteException e) {
670             cleanupAndRethrowIllegalState();
671         }
672     }
673 
674     /**
675      * Notify the CA system to refresh entitlement keys.
676      *
677      * @param refreshType the type of the refreshment.
678      * @param refreshData private data associated with the refreshment.
679      *
680      * @throws IllegalStateException if the MediaCas instance is not valid.
681      * @throws MediaCasException for CAS-specific errors.
682      * @throws MediaCasStateException for CAS-specific state exceptions.
683      */
refreshEntitlements(int refreshType, @Nullable byte[] refreshData)684     public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData)
685             throws MediaCasException {
686         validateInternalStates();
687 
688         try {
689             MediaCasException.throwExceptionIfNeeded(
690                     mICas.refreshEntitlements(refreshType, toByteArray(refreshData)));
691         } catch (RemoteException e) {
692             cleanupAndRethrowIllegalState();
693         }
694     }
695 
696     @Override
close()697     public void close() {
698         if (mICas != null) {
699             try {
700                 mICas.release();
701             } catch (RemoteException e) {
702             } finally {
703                 mICas = null;
704             }
705         }
706     }
707 
708     @Override
finalize()709     protected void finalize() {
710         close();
711     }
712 }
713