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