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