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