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.IBinder; 39 import android.os.IHwBinder; 40 import android.os.Looper; 41 import android.os.Message; 42 import android.os.Process; 43 import android.os.RemoteException; 44 import android.os.ServiceManager; 45 import android.os.ServiceSpecificException; 46 import android.util.Log; 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 IMediaCasService sService = null; 268 private static Object sAidlLock = new Object(); 269 270 /** DeathListener for AIDL service */ 271 private static IBinder.DeathRecipient sDeathListener = 272 new IBinder.DeathRecipient() { 273 @Override 274 public void binderDied() { 275 synchronized (sAidlLock) { 276 Log.d(TAG, "The service is dead"); 277 sService.asBinder().unlinkToDeath(sDeathListener, 0); 278 sService = null; 279 } 280 } 281 }; 282 getService()283 static IMediaCasService getService() { 284 synchronized (sAidlLock) { 285 if (sService == null || !sService.asBinder().isBinderAlive()) { 286 try { 287 Log.d(TAG, "Trying to get AIDL service"); 288 sService = 289 IMediaCasService.Stub.asInterface( 290 ServiceManager.waitForDeclaredService( 291 IMediaCasService.DESCRIPTOR + "/default")); 292 if (sService != null) { 293 sService.asBinder().linkToDeath(sDeathListener, 0); 294 } 295 } catch (Exception eAidl) { 296 Log.d(TAG, "Failed to get cas AIDL service"); 297 } 298 } 299 return sService; 300 } 301 } 302 303 private static android.hardware.cas.V1_0.IMediaCasService sServiceHidl = null; 304 private static Object sHidlLock = new Object(); 305 306 /** Used to indicate the right end-point to handle the serviceDied method */ 307 private static final long MEDIA_CAS_HIDL_COOKIE = 394; 308 309 /** DeathListener for HIDL service */ 310 private static IHwBinder.DeathRecipient sDeathListenerHidl = 311 new IHwBinder.DeathRecipient() { 312 @Override 313 public void serviceDied(long cookie) { 314 if (cookie == MEDIA_CAS_HIDL_COOKIE) { 315 synchronized (sHidlLock) { 316 sServiceHidl = null; 317 } 318 } 319 } 320 }; 321 getServiceHidl()322 static android.hardware.cas.V1_0.IMediaCasService getServiceHidl() { 323 synchronized (sHidlLock) { 324 if (sServiceHidl != null) { 325 return sServiceHidl; 326 } else { 327 try { 328 Log.d(TAG, "Trying to get cas@1.2 service"); 329 android.hardware.cas.V1_2.IMediaCasService serviceV12 = 330 android.hardware.cas.V1_2.IMediaCasService.getService(true /*wait*/); 331 if (serviceV12 != null) { 332 sServiceHidl = serviceV12; 333 sServiceHidl.linkToDeath(sDeathListenerHidl, MEDIA_CAS_HIDL_COOKIE); 334 return sServiceHidl; 335 } 336 } catch (Exception eV1_2) { 337 Log.d(TAG, "Failed to get cas@1.2 service"); 338 } 339 340 try { 341 Log.d(TAG, "Trying to get cas@1.1 service"); 342 android.hardware.cas.V1_1.IMediaCasService serviceV11 = 343 android.hardware.cas.V1_1.IMediaCasService.getService(true /*wait*/); 344 if (serviceV11 != null) { 345 sServiceHidl = serviceV11; 346 sServiceHidl.linkToDeath(sDeathListenerHidl, MEDIA_CAS_HIDL_COOKIE); 347 return sServiceHidl; 348 } 349 } catch (Exception eV1_1) { 350 Log.d(TAG, "Failed to get cas@1.1 service"); 351 } 352 353 try { 354 Log.d(TAG, "Trying to get cas@1.0 service"); 355 sServiceHidl = 356 android.hardware.cas.V1_0.IMediaCasService.getService(true /*wait*/); 357 if (sServiceHidl != null) { 358 sServiceHidl.linkToDeath(sDeathListenerHidl, MEDIA_CAS_HIDL_COOKIE); 359 } 360 return sServiceHidl; 361 } catch (Exception eV1_0) { 362 Log.d(TAG, "Failed to get cas@1.0 service"); 363 } 364 } 365 } 366 // Couldn't find an HIDL service, returning null. 367 return null; 368 } 369 validateInternalStates()370 private void validateInternalStates() { 371 if (mICas == null && mICasHidl == null) { 372 throw new IllegalStateException(); 373 } 374 } 375 cleanupAndRethrowIllegalState()376 private void cleanupAndRethrowIllegalState() { 377 mICas = null; 378 mICasHidl = null; 379 mICasHidl11 = null; 380 mICasHidl12 = null; 381 throw new IllegalStateException(); 382 } 383 384 private class EventHandler extends Handler { 385 386 private static final int MSG_CAS_EVENT = 0; 387 private static final int MSG_CAS_SESSION_EVENT = 1; 388 private static final int MSG_CAS_STATUS_EVENT = 2; 389 private static final int MSG_CAS_RESOURCE_LOST = 3; 390 private static final String SESSION_KEY = "sessionId"; 391 private static final String DATA_KEY = "data"; 392 EventHandler(Looper looper)393 public EventHandler(Looper looper) { 394 super(looper); 395 } 396 397 @Override handleMessage(Message msg)398 public void handleMessage(Message msg) { 399 if (msg.what == MSG_CAS_EVENT) { 400 byte[] data = (msg.obj == null) ? new byte[0] : (byte[]) msg.obj; 401 mListener.onEvent(MediaCas.this, msg.arg1, msg.arg2, data); 402 } else if (msg.what == MSG_CAS_SESSION_EVENT) { 403 Bundle bundle = msg.getData(); 404 byte[] sessionId = bundle.getByteArray(SESSION_KEY); 405 byte[] data = bundle.getByteArray(DATA_KEY); 406 mListener.onSessionEvent( 407 MediaCas.this, createFromSessionId(sessionId), msg.arg1, msg.arg2, data); 408 } else if (msg.what == MSG_CAS_STATUS_EVENT) { 409 if ((msg.arg1 == PLUGIN_STATUS_SESSION_NUMBER_CHANGED) 410 && (mTunerResourceManager != null)) { 411 mTunerResourceManager.updateCasInfo(mCasSystemId, msg.arg2); 412 } 413 mListener.onPluginStatusUpdate(MediaCas.this, msg.arg1, msg.arg2); 414 } else if (msg.what == MSG_CAS_RESOURCE_LOST) { 415 mListener.onResourceLost(MediaCas.this); 416 } 417 } 418 } 419 420 private final ICasListener.Stub mBinder = 421 new ICasListener.Stub() { 422 @Override 423 public void onEvent(int event, int arg, byte[] data) throws RemoteException { 424 if (mEventHandler != null) { 425 mEventHandler.sendMessage( 426 mEventHandler.obtainMessage( 427 EventHandler.MSG_CAS_EVENT, event, arg, data)); 428 } 429 } 430 431 @Override 432 public void onSessionEvent(byte[] sessionId, int event, int arg, byte[] data) 433 throws RemoteException { 434 if (mEventHandler != null) { 435 Message msg = mEventHandler.obtainMessage(); 436 msg.what = EventHandler.MSG_CAS_SESSION_EVENT; 437 msg.arg1 = event; 438 msg.arg2 = arg; 439 Bundle bundle = new Bundle(); 440 bundle.putByteArray(EventHandler.SESSION_KEY, sessionId); 441 bundle.putByteArray(EventHandler.DATA_KEY, data); 442 msg.setData(bundle); 443 mEventHandler.sendMessage(msg); 444 } 445 } 446 447 @Override 448 public void onStatusUpdate(byte status, int arg) throws RemoteException { 449 if (mEventHandler != null) { 450 mEventHandler.sendMessage( 451 mEventHandler.obtainMessage( 452 EventHandler.MSG_CAS_STATUS_EVENT, status, arg)); 453 } 454 } 455 456 @Override 457 public synchronized String getInterfaceHash() throws android.os.RemoteException { 458 return ICasListener.Stub.HASH; 459 } 460 461 @Override 462 public int getInterfaceVersion() throws android.os.RemoteException { 463 return ICasListener.Stub.VERSION; 464 } 465 }; 466 467 private final android.hardware.cas.V1_2.ICasListener.Stub mBinderHidl = 468 new android.hardware.cas.V1_2.ICasListener.Stub() { 469 @Override 470 public void onEvent(int event, int arg, @Nullable ArrayList<Byte> data) 471 throws RemoteException { 472 if (mEventHandler != null) { 473 mEventHandler.sendMessage( 474 mEventHandler.obtainMessage( 475 EventHandler.MSG_CAS_EVENT, event, arg, toBytes(data))); 476 } 477 } 478 479 @Override 480 public void onSessionEvent( 481 @NonNull ArrayList<Byte> sessionId, 482 int event, 483 int arg, 484 @Nullable ArrayList<Byte> data) 485 throws RemoteException { 486 if (mEventHandler != null) { 487 Message msg = mEventHandler.obtainMessage(); 488 msg.what = EventHandler.MSG_CAS_SESSION_EVENT; 489 msg.arg1 = event; 490 msg.arg2 = arg; 491 Bundle bundle = new Bundle(); 492 bundle.putByteArray(EventHandler.SESSION_KEY, toBytes(sessionId)); 493 bundle.putByteArray(EventHandler.DATA_KEY, toBytes(data)); 494 msg.setData(bundle); 495 mEventHandler.sendMessage(msg); 496 } 497 } 498 499 @Override 500 public void onStatusUpdate(byte status, int arg) throws RemoteException { 501 if (mEventHandler != null) { 502 mEventHandler.sendMessage( 503 mEventHandler.obtainMessage( 504 EventHandler.MSG_CAS_STATUS_EVENT, status, arg)); 505 } 506 } 507 }; 508 509 private final TunerResourceManager.ResourcesReclaimListener mResourceListener = 510 new TunerResourceManager.ResourcesReclaimListener() { 511 @Override 512 public void onReclaimResources() { 513 synchronized (mSessionMap) { 514 List<Session> sessionList = new ArrayList<>(mSessionMap.keySet()); 515 for (Session casSession: sessionList) { 516 casSession.close(); 517 } 518 } 519 mEventHandler.sendMessage(mEventHandler.obtainMessage( 520 EventHandler.MSG_CAS_RESOURCE_LOST)); 521 } 522 }; 523 524 /** 525 * Describe a CAS plugin with its CA_system_ID and string name. 526 * 527 * Returned as results of {@link #enumeratePlugins}. 528 * 529 */ 530 public static class PluginDescriptor { 531 private final int mCASystemId; 532 private final String mName; 533 PluginDescriptor()534 private PluginDescriptor() { 535 mCASystemId = 0xffff; 536 mName = null; 537 } 538 PluginDescriptor(@onNull AidlCasPluginDescriptor descriptor)539 PluginDescriptor(@NonNull AidlCasPluginDescriptor descriptor) { 540 mCASystemId = descriptor.caSystemId; 541 mName = descriptor.name; 542 } 543 PluginDescriptor(@onNull HidlCasPluginDescriptor descriptor)544 PluginDescriptor(@NonNull HidlCasPluginDescriptor descriptor) { 545 mCASystemId = descriptor.caSystemId; 546 mName = descriptor.name; 547 } 548 getSystemId()549 public int getSystemId() { 550 return mCASystemId; 551 } 552 553 @NonNull getName()554 public String getName() { 555 return mName; 556 } 557 558 @Override toString()559 public String toString() { 560 return "PluginDescriptor {" + mCASystemId + ", " + mName + "}"; 561 } 562 } 563 toByteArray(@onNull byte[] data, int offset, int length)564 private ArrayList<Byte> toByteArray(@NonNull byte[] data, int offset, int length) { 565 ArrayList<Byte> byteArray = new ArrayList<Byte>(length); 566 for (int i = 0; i < length; i++) { 567 byteArray.add(Byte.valueOf(data[offset + i])); 568 } 569 return byteArray; 570 } 571 toByteArray(@ullable byte[] data)572 private ArrayList<Byte> toByteArray(@Nullable byte[] data) { 573 if (data == null) { 574 return new ArrayList<Byte>(); 575 } 576 return toByteArray(data, 0, data.length); 577 } 578 toBytes(@onNull ArrayList<Byte> byteArray)579 private byte[] toBytes(@NonNull ArrayList<Byte> byteArray) { 580 byte[] data = null; 581 if (byteArray != null) { 582 data = new byte[byteArray.size()]; 583 for (int i = 0; i < data.length; i++) { 584 data[i] = byteArray.get(i); 585 } 586 } 587 return data; 588 } 589 590 /** 591 * Class for an open session with the CA system. 592 */ 593 public final class Session implements AutoCloseable { 594 final byte[] mSessionId; 595 boolean mIsClosed = false; 596 Session(@onNull byte[] sessionId)597 Session(@NonNull byte[] sessionId) { 598 mSessionId = sessionId; 599 } 600 validateSessionInternalStates()601 private void validateSessionInternalStates() { 602 if (mICas == null && mICasHidl == null) { 603 throw new IllegalStateException(); 604 } 605 if (mIsClosed) { 606 MediaCasStateException.throwExceptionIfNeeded(Status.ERROR_CAS_SESSION_NOT_OPENED); 607 } 608 } 609 610 /** 611 * Query if an object equal current Session object. 612 * 613 * @param obj an object to compare to current Session object. 614 * 615 * @return Whether input object equal current Session object. 616 */ equals(Object obj)617 public boolean equals(Object obj) { 618 if (obj instanceof Session) { 619 return Arrays.equals(mSessionId, ((Session) obj).mSessionId); 620 } 621 return false; 622 } 623 624 /** 625 * Set the private data for a session. 626 * 627 * @param data byte array of the private data. 628 * 629 * @throws IllegalStateException if the MediaCas instance is not valid. 630 * @throws MediaCasException for CAS-specific errors. 631 * @throws MediaCasStateException for CAS-specific state exceptions. 632 */ setPrivateData(@onNull byte[] data)633 public void setPrivateData(@NonNull byte[] data) 634 throws MediaCasException { 635 validateSessionInternalStates(); 636 637 try { 638 if (mICas != null) { 639 try { 640 mICas.setSessionPrivateData(mSessionId, data); 641 } catch (ServiceSpecificException se) { 642 MediaCasException.throwExceptionIfNeeded(se.errorCode); 643 } 644 } else { 645 MediaCasException.throwExceptionIfNeeded( 646 mICasHidl.setSessionPrivateData( 647 toByteArray(mSessionId), toByteArray(data, 0, data.length))); 648 } 649 } catch (RemoteException e) { 650 cleanupAndRethrowIllegalState(); 651 } 652 } 653 654 655 /** 656 * Send a received ECM packet to the specified session of the CA system. 657 * 658 * @param data byte array of the ECM data. 659 * @param offset position within data where the ECM data begins. 660 * @param length length of the data (starting from offset). 661 * 662 * @throws IllegalStateException if the MediaCas instance is not valid. 663 * @throws MediaCasException for CAS-specific errors. 664 * @throws MediaCasStateException for CAS-specific state exceptions. 665 */ processEcm(@onNull byte[] data, int offset, int length)666 public void processEcm(@NonNull byte[] data, int offset, int length) 667 throws MediaCasException { 668 validateSessionInternalStates(); 669 670 try { 671 if (mICas != null) { 672 try { 673 mICas.processEcm( 674 mSessionId, Arrays.copyOfRange(data, offset, length + offset)); 675 } catch (ServiceSpecificException se) { 676 MediaCasException.throwExceptionIfNeeded(se.errorCode); 677 } 678 } else { 679 MediaCasException.throwExceptionIfNeeded( 680 mICasHidl.processEcm( 681 toByteArray(mSessionId), toByteArray(data, offset, length))); 682 } 683 } catch (RemoteException e) { 684 cleanupAndRethrowIllegalState(); 685 } 686 } 687 688 /** 689 * Send a received ECM packet to the specified session of the CA system. 690 * This is similar to {@link Session#processEcm(byte[], int, int)} 691 * except that the entire byte array is sent. 692 * 693 * @param data byte array of the ECM data. 694 * 695 * @throws IllegalStateException if the MediaCas instance is not valid. 696 * @throws MediaCasException for CAS-specific errors. 697 * @throws MediaCasStateException for CAS-specific state exceptions. 698 */ processEcm(@onNull byte[] data)699 public void processEcm(@NonNull byte[] data) throws MediaCasException { 700 processEcm(data, 0, data.length); 701 } 702 703 /** 704 * Send a session event to a CA system. The format of the event is 705 * scheme-specific and is opaque to the framework. 706 * 707 * @param event an integer denoting a scheme-specific event to be sent. 708 * @param arg a scheme-specific integer argument for the event. 709 * @param data a byte array containing scheme-specific data for the event. 710 * 711 * @throws IllegalStateException if the MediaCas instance is not valid. 712 * @throws MediaCasException for CAS-specific errors. 713 * @throws MediaCasStateException for CAS-specific state exceptions. 714 */ sendSessionEvent(int event, int arg, @Nullable byte[] data)715 public void sendSessionEvent(int event, int arg, @Nullable byte[] data) 716 throws MediaCasException { 717 validateSessionInternalStates(); 718 if (mICas != null) { 719 try { 720 if (data == null) { 721 data = new byte[0]; 722 } 723 mICas.sendSessionEvent(mSessionId, event, arg, data); 724 } catch (RemoteException e) { 725 cleanupAndRethrowIllegalState(); 726 } 727 } else { 728 if (mICasHidl11 == null) { 729 Log.d(TAG, "Send Session Event isn't supported by cas@1.0 interface"); 730 throw new UnsupportedCasException("Send Session Event is not supported"); 731 } 732 733 try { 734 MediaCasException.throwExceptionIfNeeded( 735 mICasHidl11.sendSessionEvent( 736 toByteArray(mSessionId), event, arg, toByteArray(data))); 737 } catch (RemoteException e) { 738 cleanupAndRethrowIllegalState(); 739 } 740 } 741 } 742 743 /** 744 * Get Session Id. 745 * 746 * @return session Id of the session. 747 * 748 * @throws IllegalStateException if the MediaCas instance is not valid. 749 */ 750 @NonNull getSessionId()751 public byte[] getSessionId() { 752 validateSessionInternalStates(); 753 return mSessionId; 754 } 755 756 /** 757 * Close the session. 758 * 759 * @throws IllegalStateException if the MediaCas instance is not valid. 760 * @throws MediaCasStateException for CAS-specific state exceptions. 761 */ 762 @Override close()763 public void close() { 764 validateSessionInternalStates(); 765 try { 766 if (mICas != null) { 767 mICas.closeSession(mSessionId); 768 } else { 769 MediaCasStateException.throwExceptionIfNeeded( 770 mICasHidl.closeSession(toByteArray(mSessionId))); 771 } 772 mIsClosed = true; 773 removeSessionFromResourceMap(this); 774 } catch (RemoteException e) { 775 cleanupAndRethrowIllegalState(); 776 } 777 } 778 } 779 createFromSessionId(byte[] sessionId)780 Session createFromSessionId(byte[] sessionId) { 781 if (sessionId == null || sessionId.length == 0) { 782 return null; 783 } 784 return new Session(sessionId); 785 } 786 787 /** 788 * Query if a certain CA system is supported on this device. 789 * 790 * @param CA_system_id the id of the CA system. 791 * 792 * @return Whether the specified CA system is supported on this device. 793 */ isSystemIdSupported(int CA_system_id)794 public static boolean isSystemIdSupported(int CA_system_id) { 795 IMediaCasService service = getService(); 796 if (service != null) { 797 try { 798 return service.isSystemIdSupported(CA_system_id); 799 } catch (RemoteException e) { 800 return false; 801 } 802 } 803 804 android.hardware.cas.V1_0.IMediaCasService serviceHidl = getServiceHidl(); 805 if (serviceHidl != null) { 806 try { 807 return serviceHidl.isSystemIdSupported(CA_system_id); 808 } catch (RemoteException e) { 809 } 810 } 811 return false; 812 } 813 814 /** 815 * List all available CA plugins on the device. 816 * 817 * @return an array of descriptors for the available CA plugins. 818 */ enumeratePlugins()819 public static PluginDescriptor[] enumeratePlugins() { 820 IMediaCasService service = getService(); 821 if (service != null) { 822 try { 823 AidlCasPluginDescriptor[] descriptors = service.enumeratePlugins(); 824 if (descriptors.length == 0) { 825 return null; 826 } 827 PluginDescriptor[] results = new PluginDescriptor[descriptors.length]; 828 for (int i = 0; i < results.length; i++) { 829 results[i] = new PluginDescriptor(descriptors[i]); 830 } 831 return results; 832 } catch (RemoteException e) { 833 Log.e(TAG, "Some exception while enumerating plugins"); 834 } 835 } 836 837 android.hardware.cas.V1_0.IMediaCasService serviceHidl = getServiceHidl(); 838 if (serviceHidl != null) { 839 try { 840 ArrayList<HidlCasPluginDescriptor> descriptors = serviceHidl.enumeratePlugins(); 841 if (descriptors.size() == 0) { 842 return null; 843 } 844 PluginDescriptor[] results = new PluginDescriptor[descriptors.size()]; 845 for (int i = 0; i < results.length; i++) { 846 results[i] = new PluginDescriptor(descriptors.get(i)); 847 } 848 return results; 849 } catch (RemoteException e) { 850 } 851 } 852 return null; 853 } 854 createPlugin(int casSystemId)855 private void createPlugin(int casSystemId) throws UnsupportedCasException { 856 try { 857 mCasSystemId = casSystemId; 858 mUserId = Process.myUid(); 859 IMediaCasService service = getService(); 860 if (service != null) { 861 Log.d(TAG, "Use CAS AIDL interface to create plugin"); 862 mICas = service.createPlugin(casSystemId, mBinder); 863 } else { 864 android.hardware.cas.V1_0.IMediaCasService serviceV10 = getServiceHidl(); 865 android.hardware.cas.V1_2.IMediaCasService serviceV12 = 866 android.hardware.cas.V1_2.IMediaCasService.castFrom(serviceV10); 867 if (serviceV12 == null) { 868 android.hardware.cas.V1_1.IMediaCasService serviceV11 = 869 android.hardware.cas.V1_1.IMediaCasService.castFrom(serviceV10); 870 if (serviceV11 == null) { 871 Log.d(TAG, "Used cas@1_0 interface to create plugin"); 872 mICasHidl = serviceV10.createPlugin(casSystemId, mBinderHidl); 873 } else { 874 Log.d(TAG, "Used cas@1.1 interface to create plugin"); 875 mICasHidl = 876 mICasHidl11 = serviceV11.createPluginExt(casSystemId, mBinderHidl); 877 } 878 } else { 879 Log.d(TAG, "Used cas@1.2 interface to create plugin"); 880 mICasHidl = 881 mICasHidl11 = 882 mICasHidl12 = 883 android.hardware.cas.V1_2.ICas.castFrom( 884 serviceV12.createPluginExt( 885 casSystemId, mBinderHidl)); 886 } 887 } 888 } catch(Exception e) { 889 Log.e(TAG, "Failed to create plugin: " + e); 890 mICas = null; 891 mICasHidl = null; 892 } finally { 893 if (mICas == null && mICasHidl == null) { 894 throw new UnsupportedCasException( 895 "Unsupported casSystemId " + casSystemId); 896 } 897 } 898 } 899 registerClient(@onNull Context context, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint)900 private void registerClient(@NonNull Context context, 901 @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint) { 902 903 mTunerResourceManager = (TunerResourceManager) 904 context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE); 905 if (mTunerResourceManager != null) { 906 int[] clientId = new int[1]; 907 ResourceClientProfile profile = new ResourceClientProfile(); 908 profile.tvInputSessionId = tvInputServiceSessionId; 909 profile.useCase = priorityHint; 910 mTunerResourceManager.registerClientProfile( 911 profile, context.getMainExecutor(), mResourceListener, clientId); 912 mClientId = clientId[0]; 913 } 914 } 915 /** 916 * Instantiate a CA system of the specified system id. 917 * 918 * @param casSystemId The system id of the CA system. 919 * 920 * @throws UnsupportedCasException if the device does not support the 921 * specified CA system. 922 */ MediaCas(int casSystemId)923 public MediaCas(int casSystemId) throws UnsupportedCasException { 924 createPlugin(casSystemId); 925 } 926 927 /** 928 * Instantiate a CA system of the specified system id. 929 * 930 * @param context the context of the caller. 931 * @param casSystemId The system id of the CA system. 932 * @param tvInputServiceSessionId The Id of the session opened in TV Input Service (TIS) 933 * {@link android.media.tv.TvInputService#onCreateSession(String, String)} 934 * @param priorityHint priority hint from the use case type for new created CAS system. 935 * 936 * @throws UnsupportedCasException if the device does not support the 937 * specified CA system. 938 */ MediaCas(@onNull Context context, int casSystemId, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint)939 public MediaCas(@NonNull Context context, int casSystemId, 940 @Nullable String tvInputServiceSessionId, 941 @PriorityHintUseCaseType int priorityHint) throws UnsupportedCasException { 942 Objects.requireNonNull(context, "context must not be null"); 943 createPlugin(casSystemId); 944 registerClient(context, tvInputServiceSessionId, priorityHint); 945 } 946 /** 947 * Instantiate a CA system of the specified system id with EvenListener. 948 * 949 * @param context the context of the caller. 950 * @param casSystemId The system id of the CA system. 951 * @param tvInputServiceSessionId The Id of the session opened in TV Input Service (TIS) 952 * {@link android.media.tv.TvInputService#onCreateSession(String, String)} 953 * @param priorityHint priority hint from the use case type for new created CAS system. 954 * @param listener the event listener to be set. 955 * @param handler the handler whose looper the event listener will be called on. 956 * If handler is null, we'll try to use current thread's looper, or the main 957 * looper. If neither are available, an internal thread will be created instead. 958 * 959 * @throws UnsupportedCasException if the device does not support the 960 * specified CA system. 961 */ MediaCas(@onNull Context context, int casSystemId, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint, @Nullable Handler handler, @Nullable EventListener listener)962 public MediaCas(@NonNull Context context, int casSystemId, 963 @Nullable String tvInputServiceSessionId, 964 @PriorityHintUseCaseType int priorityHint, 965 @Nullable Handler handler, @Nullable EventListener listener) 966 throws UnsupportedCasException { 967 Objects.requireNonNull(context, "context must not be null"); 968 setEventListener(listener, handler); 969 createPlugin(casSystemId); 970 registerClient(context, tvInputServiceSessionId, priorityHint); 971 } 972 getBinder()973 IHwBinder getBinder() { 974 if (mICas != null) { 975 return null; // Return IHwBinder only for HIDL 976 } 977 978 validateInternalStates(); 979 980 return mICasHidl.asBinder(); 981 } 982 983 /** 984 * Check if the HAL is an AIDL implementation. For CTS testing purpose. 985 * 986 * @hide 987 */ 988 @TestApi isAidlHal()989 public boolean isAidlHal() { 990 return mICas != null; 991 } 992 993 /** 994 * An interface registered by the caller to {@link #setEventListener} 995 * to receives scheme-specific notifications from a MediaCas instance. 996 */ 997 public interface EventListener { 998 999 /** 1000 * Notify the listener of a scheme-specific event from the CA system. 1001 * 1002 * @param mediaCas the MediaCas object to receive this event. 1003 * @param event an integer whose meaning is scheme-specific. 1004 * @param arg an integer whose meaning is scheme-specific. 1005 * @param data a byte array of data whose format and meaning are 1006 * scheme-specific. 1007 */ onEvent(@onNull MediaCas mediaCas, int event, int arg, @Nullable byte[] data)1008 void onEvent(@NonNull MediaCas mediaCas, int event, int arg, @Nullable byte[] data); 1009 1010 /** 1011 * Notify the listener of a scheme-specific session event from CA system. 1012 * 1013 * @param mediaCas the MediaCas object to receive this event. 1014 * @param session session object which the event is for. 1015 * @param event an integer whose meaning is scheme-specific. 1016 * @param arg an integer whose meaning is scheme-specific. 1017 * @param data a byte array of data whose format and meaning are 1018 * scheme-specific. 1019 */ onSessionEvent(@onNull MediaCas mediaCas, @NonNull Session session, int event, int arg, @Nullable byte[] data)1020 default void onSessionEvent(@NonNull MediaCas mediaCas, @NonNull Session session, 1021 int event, int arg, @Nullable byte[] data) { 1022 Log.d(TAG, "Received MediaCas Session event"); 1023 } 1024 1025 /** 1026 * Notify the listener that the cas plugin status is updated. 1027 * 1028 * @param mediaCas the MediaCas object to receive this event. 1029 * @param status the plugin status which is updated. 1030 * @param arg an integer whose meaning is specific to the status to be updated. 1031 */ onPluginStatusUpdate(@onNull MediaCas mediaCas, @PluginStatus int status, int arg)1032 default void onPluginStatusUpdate(@NonNull MediaCas mediaCas, @PluginStatus int status, 1033 int arg) { 1034 Log.d(TAG, "Received MediaCas Plugin Status event"); 1035 } 1036 1037 /** 1038 * Notify the listener that the session resources was lost. 1039 * 1040 * @param mediaCas the MediaCas object to receive this event. 1041 */ onResourceLost(@onNull MediaCas mediaCas)1042 default void onResourceLost(@NonNull MediaCas mediaCas) { 1043 Log.d(TAG, "Received MediaCas Resource Reclaim event"); 1044 } 1045 } 1046 1047 /** 1048 * Set an event listener to receive notifications from the MediaCas instance. 1049 * 1050 * @param listener the event listener to be set. 1051 * @param handler the handler whose looper the event listener will be called on. 1052 * If handler is null, we'll try to use current thread's looper, or the main 1053 * looper. If neither are available, an internal thread will be created instead. 1054 */ setEventListener( @ullable EventListener listener, @Nullable Handler handler)1055 public void setEventListener( 1056 @Nullable EventListener listener, @Nullable Handler handler) { 1057 mListener = listener; 1058 1059 if (mListener == null) { 1060 mEventHandler = null; 1061 return; 1062 } 1063 1064 Looper looper = (handler != null) ? handler.getLooper() : null; 1065 if (looper == null 1066 && (looper = Looper.myLooper()) == null 1067 && (looper = Looper.getMainLooper()) == null) { 1068 if (mHandlerThread == null || !mHandlerThread.isAlive()) { 1069 mHandlerThread = new HandlerThread("MediaCasEventThread", 1070 Process.THREAD_PRIORITY_FOREGROUND); 1071 mHandlerThread.start(); 1072 } 1073 looper = mHandlerThread.getLooper(); 1074 } 1075 mEventHandler = new EventHandler(looper); 1076 } 1077 1078 /** 1079 * Send the private data for the CA system. 1080 * 1081 * @param data byte array of the private data. 1082 * 1083 * @throws IllegalStateException if the MediaCas instance is not valid. 1084 * @throws MediaCasException for CAS-specific errors. 1085 * @throws MediaCasStateException for CAS-specific state exceptions. 1086 */ setPrivateData(@onNull byte[] data)1087 public void setPrivateData(@NonNull byte[] data) throws MediaCasException { 1088 validateInternalStates(); 1089 1090 try { 1091 if (mICas != null) { 1092 try { 1093 mICas.setPrivateData(data); 1094 } catch (ServiceSpecificException se) { 1095 MediaCasException.throwExceptionIfNeeded(se.errorCode); 1096 } 1097 } else { 1098 MediaCasException.throwExceptionIfNeeded( 1099 mICasHidl.setPrivateData(toByteArray(data, 0, data.length))); 1100 } 1101 } catch (RemoteException e) { 1102 cleanupAndRethrowIllegalState(); 1103 } 1104 } 1105 1106 private class OpenSessionCallback implements android.hardware.cas.V1_1.ICas.openSessionCallback{ 1107 public Session mSession; 1108 public int mStatus; 1109 @Override onValues(int status, ArrayList<Byte> sessionId)1110 public void onValues(int status, ArrayList<Byte> sessionId) { 1111 mStatus = status; 1112 mSession = createFromSessionId(toBytes(sessionId)); 1113 } 1114 } 1115 1116 private class OpenSession_1_2_Callback implements 1117 android.hardware.cas.V1_2.ICas.openSession_1_2Callback { 1118 1119 public Session mSession; 1120 public int mStatus; 1121 1122 @Override onValues(int status, ArrayList<Byte> sessionId)1123 public void onValues(int status, ArrayList<Byte> sessionId) { 1124 mStatus = status; 1125 mSession = createFromSessionId(toBytes(sessionId)); 1126 } 1127 } 1128 getSessionResourceHandle()1129 private int getSessionResourceHandle() throws MediaCasException { 1130 validateInternalStates(); 1131 1132 int[] sessionResourceHandle = new int[1]; 1133 sessionResourceHandle[0] = -1; 1134 if (mTunerResourceManager != null) { 1135 CasSessionRequest casSessionRequest = new CasSessionRequest(); 1136 casSessionRequest.clientId = mClientId; 1137 casSessionRequest.casSystemId = mCasSystemId; 1138 if (!mTunerResourceManager 1139 .requestCasSession(casSessionRequest, sessionResourceHandle)) { 1140 throw new MediaCasException.InsufficientResourceException( 1141 "insufficient resource to Open Session"); 1142 } 1143 } 1144 return sessionResourceHandle[0]; 1145 } 1146 addSessionToResourceMap(Session session, int sessionResourceHandle)1147 private void addSessionToResourceMap(Session session, int sessionResourceHandle) { 1148 1149 if (sessionResourceHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) { 1150 synchronized (mSessionMap) { 1151 mSessionMap.put(session, sessionResourceHandle); 1152 } 1153 } 1154 } 1155 removeSessionFromResourceMap(Session session)1156 private void removeSessionFromResourceMap(Session session) { 1157 1158 synchronized (mSessionMap) { 1159 if (mSessionMap.get(session) != null) { 1160 mTunerResourceManager.releaseCasSession(mSessionMap.get(session), mClientId); 1161 mSessionMap.remove(session); 1162 } 1163 } 1164 } 1165 1166 /** 1167 * Open a session to descramble one or more streams scrambled by the 1168 * conditional access system. 1169 * 1170 * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able 1171 * to get cas session resource if cas session resources is limited. If the client can't get the 1172 * resource, this call returns {@link MediaCasException.InsufficientResourceException }. 1173 * 1174 * @return session the newly opened session. 1175 * 1176 * @throws IllegalStateException if the MediaCas instance is not valid. 1177 * @throws MediaCasException for CAS-specific errors. 1178 * @throws MediaCasStateException for CAS-specific state exceptions. 1179 */ openSession()1180 public Session openSession() throws MediaCasException { 1181 int sessionResourceHandle = getSessionResourceHandle(); 1182 1183 try { 1184 if (mICas != null) { 1185 try { 1186 byte[] sessionId = mICas.openSessionDefault(); 1187 Session session = createFromSessionId(sessionId); 1188 Log.d(TAG, "Write Stats Log for succeed to Open Session."); 1189 FrameworkStatsLog.write( 1190 FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, 1191 mUserId, 1192 mCasSystemId, 1193 FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED); 1194 return session; 1195 } catch (ServiceSpecificException se) { 1196 MediaCasException.throwExceptionIfNeeded(se.errorCode); 1197 } 1198 } else if (mICasHidl != null) { 1199 OpenSessionCallback cb = new OpenSessionCallback(); 1200 mICasHidl.openSession(cb); 1201 MediaCasException.throwExceptionIfNeeded(cb.mStatus); 1202 addSessionToResourceMap(cb.mSession, sessionResourceHandle); 1203 Log.d(TAG, "Write Stats Log for succeed to Open Session."); 1204 FrameworkStatsLog.write( 1205 FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, 1206 mUserId, 1207 mCasSystemId, 1208 FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED); 1209 return cb.mSession; 1210 } 1211 } catch (RemoteException e) { 1212 cleanupAndRethrowIllegalState(); 1213 } 1214 Log.d(TAG, "Write Stats Log for fail to Open Session."); 1215 FrameworkStatsLog 1216 .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, 1217 FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED); 1218 return null; 1219 } 1220 1221 /** 1222 * Open a session with usage and scrambling information, so that descrambler can be configured 1223 * to descramble one or more streams scrambled by the conditional access system. 1224 * 1225 * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able 1226 * to get cas session resource if cas session resources is limited. If the client can't get the 1227 * resource, this call returns {@link MediaCasException.InsufficientResourceException}. 1228 * 1229 * @param sessionUsage used for the created session. 1230 * @param scramblingMode used for the created session. 1231 * 1232 * @return session the newly opened session. 1233 * 1234 * @throws IllegalStateException if the MediaCas instance is not valid. 1235 * @throws MediaCasException for CAS-specific errors. 1236 * @throws MediaCasStateException for CAS-specific state exceptions. 1237 */ 1238 @Nullable openSession(@essionUsage int sessionUsage, @ScramblingMode int scramblingMode)1239 public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode) 1240 throws MediaCasException { 1241 int sessionResourceHandle = getSessionResourceHandle(); 1242 1243 if (mICas != null) { 1244 try { 1245 byte[] sessionId = mICas.openSession(sessionUsage, scramblingMode); 1246 Session session = createFromSessionId(sessionId); 1247 addSessionToResourceMap(session, sessionResourceHandle); 1248 Log.d(TAG, "Write Stats Log for succeed to Open Session."); 1249 FrameworkStatsLog.write( 1250 FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, 1251 mUserId, 1252 mCasSystemId, 1253 FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED); 1254 return session; 1255 } catch (ServiceSpecificException | RemoteException e) { 1256 cleanupAndRethrowIllegalState(); 1257 } 1258 } 1259 if (mICasHidl12 == null) { 1260 Log.d(TAG, "Open Session with scrambling mode is only supported by cas@1.2+ interface"); 1261 throw new UnsupportedCasException("Open Session with scrambling mode is not supported"); 1262 } 1263 1264 try { 1265 OpenSession_1_2_Callback cb = new OpenSession_1_2_Callback(); 1266 mICasHidl12.openSession_1_2(sessionUsage, scramblingMode, cb); 1267 MediaCasException.throwExceptionIfNeeded(cb.mStatus); 1268 addSessionToResourceMap(cb.mSession, sessionResourceHandle); 1269 Log.d(TAG, "Write Stats Log for succeed to Open Session."); 1270 FrameworkStatsLog 1271 .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, 1272 FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED); 1273 return cb.mSession; 1274 } catch (RemoteException e) { 1275 cleanupAndRethrowIllegalState(); 1276 } 1277 Log.d(TAG, "Write Stats Log for fail to Open Session."); 1278 FrameworkStatsLog 1279 .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, 1280 FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED); 1281 return null; 1282 } 1283 1284 /** 1285 * Send a received EMM packet to the CA system. 1286 * 1287 * @param data byte array of the EMM data. 1288 * @param offset position within data where the EMM data begins. 1289 * @param length length of the data (starting from offset). 1290 * 1291 * @throws IllegalStateException if the MediaCas instance is not valid. 1292 * @throws MediaCasException for CAS-specific errors. 1293 * @throws MediaCasStateException for CAS-specific state exceptions. 1294 */ processEmm(@onNull byte[] data, int offset, int length)1295 public void processEmm(@NonNull byte[] data, int offset, int length) 1296 throws MediaCasException { 1297 validateInternalStates(); 1298 1299 try { 1300 if (mICas != null) { 1301 try { 1302 mICas.processEmm(Arrays.copyOfRange(data, offset, length)); 1303 } catch (ServiceSpecificException se) { 1304 MediaCasException.throwExceptionIfNeeded(se.errorCode); 1305 } 1306 } else { 1307 MediaCasException.throwExceptionIfNeeded( 1308 mICasHidl.processEmm(toByteArray(data, offset, length))); 1309 } 1310 } catch (RemoteException e) { 1311 cleanupAndRethrowIllegalState(); 1312 } 1313 } 1314 1315 /** 1316 * Send a received EMM packet to the CA system. This is similar to 1317 * {@link #processEmm(byte[], int, int)} except that the entire byte 1318 * array is sent. 1319 * 1320 * @param data byte array of the EMM data. 1321 * 1322 * @throws IllegalStateException if the MediaCas instance is not valid. 1323 * @throws MediaCasException for CAS-specific errors. 1324 * @throws MediaCasStateException for CAS-specific state exceptions. 1325 */ processEmm(@onNull byte[] data)1326 public void processEmm(@NonNull byte[] data) throws MediaCasException { 1327 processEmm(data, 0, data.length); 1328 } 1329 1330 /** 1331 * Send an event to a CA system. The format of the event is scheme-specific 1332 * and is opaque to the framework. 1333 * 1334 * @param event an integer denoting a scheme-specific event to be sent. 1335 * @param arg a scheme-specific integer argument for the event. 1336 * @param data a byte array containing scheme-specific data for the event. 1337 * 1338 * @throws IllegalStateException if the MediaCas instance is not valid. 1339 * @throws MediaCasException for CAS-specific errors. 1340 * @throws MediaCasStateException for CAS-specific state exceptions. 1341 */ sendEvent(int event, int arg, @Nullable byte[] data)1342 public void sendEvent(int event, int arg, @Nullable byte[] data) 1343 throws MediaCasException { 1344 validateInternalStates(); 1345 1346 try { 1347 if (mICas != null) { 1348 try { 1349 if (data == null) { 1350 data = new byte[0]; 1351 } 1352 mICas.sendEvent(event, arg, data); 1353 } catch (ServiceSpecificException se) { 1354 MediaCasException.throwExceptionIfNeeded(se.errorCode); 1355 } 1356 } else { 1357 MediaCasException.throwExceptionIfNeeded( 1358 mICasHidl.sendEvent(event, arg, toByteArray(data))); 1359 } 1360 } catch (RemoteException e) { 1361 cleanupAndRethrowIllegalState(); 1362 } 1363 } 1364 1365 /** 1366 * Initiate a provisioning operation for a CA system. 1367 * 1368 * @param provisionString string containing information needed for the 1369 * provisioning operation, the format of which is scheme and implementation 1370 * specific. 1371 * 1372 * @throws IllegalStateException if the MediaCas instance is not valid. 1373 * @throws MediaCasException for CAS-specific errors. 1374 * @throws MediaCasStateException for CAS-specific state exceptions. 1375 */ provision(@onNull String provisionString)1376 public void provision(@NonNull String provisionString) throws MediaCasException { 1377 validateInternalStates(); 1378 1379 try { 1380 if (mICas != null) { 1381 try { 1382 mICas.provision(provisionString); 1383 } catch (ServiceSpecificException se) { 1384 MediaCasException.throwExceptionIfNeeded(se.errorCode); 1385 } 1386 } else { 1387 MediaCasException.throwExceptionIfNeeded(mICasHidl.provision(provisionString)); 1388 } 1389 } catch (RemoteException e) { 1390 cleanupAndRethrowIllegalState(); 1391 } 1392 } 1393 1394 /** 1395 * Notify the CA system to refresh entitlement keys. 1396 * 1397 * @param refreshType the type of the refreshment. 1398 * @param refreshData private data associated with the refreshment. 1399 * 1400 * @throws IllegalStateException if the MediaCas instance is not valid. 1401 * @throws MediaCasException for CAS-specific errors. 1402 * @throws MediaCasStateException for CAS-specific state exceptions. 1403 */ refreshEntitlements(int refreshType, @Nullable byte[] refreshData)1404 public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData) 1405 throws MediaCasException { 1406 validateInternalStates(); 1407 1408 try { 1409 if (mICas != null) { 1410 try { 1411 if (refreshData == null) { 1412 refreshData = new byte[0]; 1413 } 1414 mICas.refreshEntitlements(refreshType, refreshData); 1415 } catch (ServiceSpecificException se) { 1416 MediaCasException.throwExceptionIfNeeded(se.errorCode); 1417 } 1418 } else { 1419 MediaCasException.throwExceptionIfNeeded( 1420 mICasHidl.refreshEntitlements(refreshType, toByteArray(refreshData))); 1421 } 1422 } catch (RemoteException e) { 1423 cleanupAndRethrowIllegalState(); 1424 } 1425 } 1426 1427 /** 1428 * Release Cas session. This is primarily used as a test API for CTS. 1429 * @hide 1430 */ 1431 @TestApi forceResourceLost()1432 public void forceResourceLost() { 1433 if (mResourceListener != null) { 1434 mResourceListener.onReclaimResources(); 1435 } 1436 } 1437 1438 @Override close()1439 public void close() { 1440 if (mICas != null) { 1441 try { 1442 mICas.release(); 1443 } catch (RemoteException e) { 1444 } finally { 1445 mICas = null; 1446 } 1447 } else if (mICasHidl != null) { 1448 try { 1449 mICasHidl.release(); 1450 } catch (RemoteException e) { 1451 } finally { 1452 mICasHidl = mICasHidl11 = mICasHidl12 = null; 1453 } 1454 } 1455 1456 if (mTunerResourceManager != null) { 1457 mTunerResourceManager.unregisterClientProfile(mClientId); 1458 mTunerResourceManager = null; 1459 } 1460 1461 if (mHandlerThread != null) { 1462 mHandlerThread.quit(); 1463 mHandlerThread = null; 1464 } 1465 } 1466 1467 @Override finalize()1468 protected void finalize() { 1469 close(); 1470 } 1471 } 1472