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