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