1 /* 2 * Copyright (C) 2016 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 package com.google.android.exoplayer2.drm; 17 18 import android.annotation.SuppressLint; 19 import android.annotation.TargetApi; 20 import android.media.DeniedByServerException; 21 import android.media.MediaDrm; 22 import android.media.NotProvisionedException; 23 import android.os.Handler; 24 import android.os.HandlerThread; 25 import android.os.Looper; 26 import android.os.Message; 27 import android.support.annotation.IntDef; 28 import android.text.TextUtils; 29 import android.util.Log; 30 import android.util.Pair; 31 import com.google.android.exoplayer2.C; 32 import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; 33 import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest; 34 import com.google.android.exoplayer2.drm.ExoMediaDrm.OnEventListener; 35 import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; 36 import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; 37 import com.google.android.exoplayer2.util.Assertions; 38 import com.google.android.exoplayer2.util.MimeTypes; 39 import com.google.android.exoplayer2.util.Util; 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.util.HashMap; 43 import java.util.Map; 44 import java.util.UUID; 45 46 /** 47 * A {@link DrmSessionManager} that supports playbacks using {@link MediaDrm}. 48 */ 49 @TargetApi(18) 50 public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager<T>, 51 DrmSession<T> { 52 53 /** 54 * Listener of {@link DefaultDrmSessionManager} events. 55 */ 56 public interface EventListener { 57 58 /** 59 * Called each time keys are loaded. 60 */ onDrmKeysLoaded()61 void onDrmKeysLoaded(); 62 63 /** 64 * Called when a drm error occurs. 65 * 66 * @param e The corresponding exception. 67 */ onDrmSessionManagerError(Exception e)68 void onDrmSessionManagerError(Exception e); 69 70 /** 71 * Called each time offline keys are restored. 72 */ onDrmKeysRestored()73 void onDrmKeysRestored(); 74 75 /** 76 * Called each time offline keys are removed. 77 */ onDrmKeysRemoved()78 void onDrmKeysRemoved(); 79 80 } 81 82 /** 83 * The key to use when passing CustomData to a PlayReady instance in an optional parameter map. 84 */ 85 public static final String PLAYREADY_CUSTOM_DATA_KEY = "PRCustomData"; 86 87 /** Determines the action to be done after a session acquired. */ 88 @Retention(RetentionPolicy.SOURCE) 89 @IntDef({MODE_PLAYBACK, MODE_QUERY, MODE_DOWNLOAD, MODE_RELEASE}) 90 public @interface Mode {} 91 /** 92 * Loads and refreshes (if necessary) a license for playback. Supports streaming and offline 93 * licenses. 94 */ 95 public static final int MODE_PLAYBACK = 0; 96 /** 97 * Restores an offline license to allow its status to be queried. If the offline license is 98 * expired sets state to {@link #STATE_ERROR}. 99 */ 100 public static final int MODE_QUERY = 1; 101 /** Downloads an offline license or renews an existing one. */ 102 public static final int MODE_DOWNLOAD = 2; 103 /** Releases an existing offline license. */ 104 public static final int MODE_RELEASE = 3; 105 106 private static final String TAG = "OfflineDrmSessionMngr"; 107 private static final String CENC_SCHEME_MIME_TYPE = "cenc"; 108 109 private static final int MSG_PROVISION = 0; 110 private static final int MSG_KEYS = 1; 111 112 private static final int MAX_LICENSE_DURATION_TO_RENEW = 60; 113 114 private final Handler eventHandler; 115 private final EventListener eventListener; 116 private final ExoMediaDrm<T> mediaDrm; 117 private final HashMap<String, String> optionalKeyRequestParameters; 118 119 /* package */ final MediaDrmCallback callback; 120 /* package */ final UUID uuid; 121 122 /* package */ MediaDrmHandler mediaDrmHandler; 123 /* package */ PostResponseHandler postResponseHandler; 124 125 private Looper playbackLooper; 126 private HandlerThread requestHandlerThread; 127 private Handler postRequestHandler; 128 129 private int mode; 130 private int openCount; 131 private boolean provisioningInProgress; 132 @DrmSession.State 133 private int state; 134 private T mediaCrypto; 135 private DrmSessionException lastException; 136 private byte[] schemeInitData; 137 private String schemeMimeType; 138 private byte[] sessionId; 139 private byte[] offlineLicenseKeySetId; 140 141 /** 142 * Instantiates a new instance using the Widevine scheme. 143 * 144 * @param callback Performs key and provisioning requests. 145 * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument 146 * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. 147 * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be 148 * null if delivery of events is not required. 149 * @param eventListener A listener of events. May be null if delivery of events is not required. 150 * @throws UnsupportedDrmException If the specified DRM scheme is not supported. 151 */ newWidevineInstance( MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters, Handler eventHandler, EventListener eventListener)152 public static DefaultDrmSessionManager<FrameworkMediaCrypto> newWidevineInstance( 153 MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters, 154 Handler eventHandler, EventListener eventListener) throws UnsupportedDrmException { 155 return newFrameworkInstance(C.WIDEVINE_UUID, callback, optionalKeyRequestParameters, 156 eventHandler, eventListener); 157 } 158 159 /** 160 * Instantiates a new instance using the PlayReady scheme. 161 * <p> 162 * Note that PlayReady is unsupported by most Android devices, with the exception of Android TV 163 * devices, which do provide support. 164 * 165 * @param callback Performs key and provisioning requests. 166 * @param customData Optional custom data to include in requests generated by the instance. 167 * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be 168 * null if delivery of events is not required. 169 * @param eventListener A listener of events. May be null if delivery of events is not required. 170 * @throws UnsupportedDrmException If the specified DRM scheme is not supported. 171 */ newPlayReadyInstance( MediaDrmCallback callback, String customData, Handler eventHandler, EventListener eventListener)172 public static DefaultDrmSessionManager<FrameworkMediaCrypto> newPlayReadyInstance( 173 MediaDrmCallback callback, String customData, Handler eventHandler, 174 EventListener eventListener) throws UnsupportedDrmException { 175 HashMap<String, String> optionalKeyRequestParameters; 176 if (!TextUtils.isEmpty(customData)) { 177 optionalKeyRequestParameters = new HashMap<>(); 178 optionalKeyRequestParameters.put(PLAYREADY_CUSTOM_DATA_KEY, customData); 179 } else { 180 optionalKeyRequestParameters = null; 181 } 182 return newFrameworkInstance(C.PLAYREADY_UUID, callback, optionalKeyRequestParameters, 183 eventHandler, eventListener); 184 } 185 186 /** 187 * Instantiates a new instance. 188 * 189 * @param uuid The UUID of the drm scheme. 190 * @param callback Performs key and provisioning requests. 191 * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument 192 * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. 193 * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be 194 * null if delivery of events is not required. 195 * @param eventListener A listener of events. May be null if delivery of events is not required. 196 * @throws UnsupportedDrmException If the specified DRM scheme is not supported. 197 */ newFrameworkInstance( UUID uuid, MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters, Handler eventHandler, EventListener eventListener)198 public static DefaultDrmSessionManager<FrameworkMediaCrypto> newFrameworkInstance( 199 UUID uuid, MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters, 200 Handler eventHandler, EventListener eventListener) throws UnsupportedDrmException { 201 return new DefaultDrmSessionManager<>(uuid, FrameworkMediaDrm.newInstance(uuid), callback, 202 optionalKeyRequestParameters, eventHandler, eventListener); 203 } 204 205 /** 206 * @param uuid The UUID of the drm scheme. 207 * @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager. 208 * @param callback Performs key and provisioning requests. 209 * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument 210 * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. 211 * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be 212 * null if delivery of events is not required. 213 * @param eventListener A listener of events. May be null if delivery of events is not required. 214 */ DefaultDrmSessionManager(UUID uuid, ExoMediaDrm<T> mediaDrm, MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters, Handler eventHandler, EventListener eventListener)215 public DefaultDrmSessionManager(UUID uuid, ExoMediaDrm<T> mediaDrm, MediaDrmCallback callback, 216 HashMap<String, String> optionalKeyRequestParameters, Handler eventHandler, 217 EventListener eventListener) { 218 this.uuid = uuid; 219 this.mediaDrm = mediaDrm; 220 this.callback = callback; 221 this.optionalKeyRequestParameters = optionalKeyRequestParameters; 222 this.eventHandler = eventHandler; 223 this.eventListener = eventListener; 224 mediaDrm.setOnEventListener(new MediaDrmEventListener()); 225 state = STATE_CLOSED; 226 mode = MODE_PLAYBACK; 227 } 228 229 /** 230 * Provides access to {@link MediaDrm#getPropertyString(String)}. 231 * <p> 232 * This method may be called when the manager is in any state. 233 * 234 * @param key The key to request. 235 * @return The retrieved property. 236 */ getPropertyString(String key)237 public final String getPropertyString(String key) { 238 return mediaDrm.getPropertyString(key); 239 } 240 241 /** 242 * Provides access to {@link MediaDrm#setPropertyString(String, String)}. 243 * <p> 244 * This method may be called when the manager is in any state. 245 * 246 * @param key The property to write. 247 * @param value The value to write. 248 */ setPropertyString(String key, String value)249 public final void setPropertyString(String key, String value) { 250 mediaDrm.setPropertyString(key, value); 251 } 252 253 /** 254 * Provides access to {@link MediaDrm#getPropertyByteArray(String)}. 255 * <p> 256 * This method may be called when the manager is in any state. 257 * 258 * @param key The key to request. 259 * @return The retrieved property. 260 */ getPropertyByteArray(String key)261 public final byte[] getPropertyByteArray(String key) { 262 return mediaDrm.getPropertyByteArray(key); 263 } 264 265 /** 266 * Provides access to {@link MediaDrm#setPropertyByteArray(String, byte[])}. 267 * <p> 268 * This method may be called when the manager is in any state. 269 * 270 * @param key The property to write. 271 * @param value The value to write. 272 */ setPropertyByteArray(String key, byte[] value)273 public final void setPropertyByteArray(String key, byte[] value) { 274 mediaDrm.setPropertyByteArray(key, value); 275 } 276 277 /** 278 * Sets the mode, which determines the role of sessions acquired from the instance. This must be 279 * called before {@link #acquireSession(Looper, DrmInitData)} is called. 280 * 281 * <p>By default, the mode is {@link #MODE_PLAYBACK} and a streaming license is requested when 282 * required. 283 * 284 * <p>{@code mode} must be one of these: 285 * <ul> 286 * <li>{@link #MODE_PLAYBACK}: If {@code offlineLicenseKeySetId} is null, a streaming license is 287 * requested otherwise the offline license is restored. 288 * <li>{@link #MODE_QUERY}: {@code offlineLicenseKeySetId} can not be null. The offline license 289 * is restored. 290 * <li>{@link #MODE_DOWNLOAD}: If {@code offlineLicenseKeySetId} is null, an offline license is 291 * requested otherwise the offline license is renewed. 292 * <li>{@link #MODE_RELEASE}: {@code offlineLicenseKeySetId} can not be null. The offline license 293 * is released. 294 * </ul> 295 * 296 * @param mode The mode to be set. 297 * @param offlineLicenseKeySetId The key set id of the license to be used with the given mode. 298 */ setMode(@ode int mode, byte[] offlineLicenseKeySetId)299 public void setMode(@Mode int mode, byte[] offlineLicenseKeySetId) { 300 Assertions.checkState(openCount == 0); 301 if (mode == MODE_QUERY || mode == MODE_RELEASE) { 302 Assertions.checkNotNull(offlineLicenseKeySetId); 303 } 304 this.mode = mode; 305 this.offlineLicenseKeySetId = offlineLicenseKeySetId; 306 } 307 308 // DrmSessionManager implementation. 309 310 @Override acquireSession(Looper playbackLooper, DrmInitData drmInitData)311 public DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitData) { 312 Assertions.checkState(this.playbackLooper == null || this.playbackLooper == playbackLooper); 313 if (++openCount != 1) { 314 return this; 315 } 316 317 if (this.playbackLooper == null) { 318 this.playbackLooper = playbackLooper; 319 mediaDrmHandler = new MediaDrmHandler(playbackLooper); 320 postResponseHandler = new PostResponseHandler(playbackLooper); 321 } 322 323 requestHandlerThread = new HandlerThread("DrmRequestHandler"); 324 requestHandlerThread.start(); 325 postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper()); 326 327 if (offlineLicenseKeySetId == null) { 328 SchemeData schemeData = drmInitData.get(uuid); 329 if (schemeData == null) { 330 onError(new IllegalStateException("Media does not support uuid: " + uuid)); 331 return this; 332 } 333 schemeInitData = schemeData.data; 334 schemeMimeType = schemeData.mimeType; 335 if (Util.SDK_INT < 21) { 336 // Prior to L the Widevine CDM required data to be extracted from the PSSH atom. 337 byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(schemeInitData, C.WIDEVINE_UUID); 338 if (psshData == null) { 339 // Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged. 340 } else { 341 schemeInitData = psshData; 342 } 343 } 344 if (Util.SDK_INT < 26 && C.CLEARKEY_UUID.equals(uuid) 345 && (MimeTypes.VIDEO_MP4.equals(schemeMimeType) 346 || MimeTypes.AUDIO_MP4.equals(schemeMimeType))) { 347 // Prior to API level 26 the ClearKey CDM only accepted "cenc" as the scheme for MP4. 348 schemeMimeType = CENC_SCHEME_MIME_TYPE; 349 } 350 } 351 state = STATE_OPENING; 352 openInternal(true); 353 return this; 354 } 355 356 @Override releaseSession(DrmSession<T> session)357 public void releaseSession(DrmSession<T> session) { 358 if (--openCount != 0) { 359 return; 360 } 361 state = STATE_CLOSED; 362 provisioningInProgress = false; 363 mediaDrmHandler.removeCallbacksAndMessages(null); 364 postResponseHandler.removeCallbacksAndMessages(null); 365 postRequestHandler.removeCallbacksAndMessages(null); 366 postRequestHandler = null; 367 requestHandlerThread.quit(); 368 requestHandlerThread = null; 369 schemeInitData = null; 370 schemeMimeType = null; 371 mediaCrypto = null; 372 lastException = null; 373 if (sessionId != null) { 374 mediaDrm.closeSession(sessionId); 375 sessionId = null; 376 } 377 } 378 379 // DrmSession implementation. 380 381 @Override 382 @DrmSession.State getState()383 public final int getState() { 384 return state; 385 } 386 387 @Override getMediaCrypto()388 public final T getMediaCrypto() { 389 if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { 390 throw new IllegalStateException(); 391 } 392 return mediaCrypto; 393 } 394 395 @Override requiresSecureDecoderComponent(String mimeType)396 public boolean requiresSecureDecoderComponent(String mimeType) { 397 if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { 398 throw new IllegalStateException(); 399 } 400 return mediaCrypto.requiresSecureDecoderComponent(mimeType); 401 } 402 403 @Override getError()404 public final DrmSessionException getError() { 405 return state == STATE_ERROR ? lastException : null; 406 } 407 408 @Override queryKeyStatus()409 public Map<String, String> queryKeyStatus() { 410 // User may call this method rightfully even if state == STATE_ERROR. So only check if there is 411 // a sessionId 412 if (sessionId == null) { 413 throw new IllegalStateException(); 414 } 415 return mediaDrm.queryKeyStatus(sessionId); 416 } 417 418 @Override getOfflineLicenseKeySetId()419 public byte[] getOfflineLicenseKeySetId() { 420 return offlineLicenseKeySetId; 421 } 422 423 // Internal methods. 424 openInternal(boolean allowProvisioning)425 private void openInternal(boolean allowProvisioning) { 426 try { 427 sessionId = mediaDrm.openSession(); 428 mediaCrypto = mediaDrm.createMediaCrypto(uuid, sessionId); 429 state = STATE_OPENED; 430 doLicense(); 431 } catch (NotProvisionedException e) { 432 if (allowProvisioning) { 433 postProvisionRequest(); 434 } else { 435 onError(e); 436 } 437 } catch (Exception e) { 438 onError(e); 439 } 440 } 441 postProvisionRequest()442 private void postProvisionRequest() { 443 if (provisioningInProgress) { 444 return; 445 } 446 provisioningInProgress = true; 447 ProvisionRequest request = mediaDrm.getProvisionRequest(); 448 postRequestHandler.obtainMessage(MSG_PROVISION, request).sendToTarget(); 449 } 450 onProvisionResponse(Object response)451 private void onProvisionResponse(Object response) { 452 provisioningInProgress = false; 453 if (state != STATE_OPENING && state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { 454 // This event is stale. 455 return; 456 } 457 458 if (response instanceof Exception) { 459 onError((Exception) response); 460 return; 461 } 462 463 try { 464 mediaDrm.provideProvisionResponse((byte[]) response); 465 if (state == STATE_OPENING) { 466 openInternal(false); 467 } else { 468 doLicense(); 469 } 470 } catch (DeniedByServerException e) { 471 onError(e); 472 } 473 } 474 doLicense()475 private void doLicense() { 476 switch (mode) { 477 case MODE_PLAYBACK: 478 case MODE_QUERY: 479 if (offlineLicenseKeySetId == null) { 480 postKeyRequest(sessionId, MediaDrm.KEY_TYPE_STREAMING); 481 } else { 482 if (restoreKeys()) { 483 long licenseDurationRemainingSec = getLicenseDurationRemainingSec(); 484 if (mode == MODE_PLAYBACK 485 && licenseDurationRemainingSec <= MAX_LICENSE_DURATION_TO_RENEW) { 486 Log.d(TAG, "Offline license has expired or will expire soon. " 487 + "Remaining seconds: " + licenseDurationRemainingSec); 488 postKeyRequest(sessionId, MediaDrm.KEY_TYPE_OFFLINE); 489 } else if (licenseDurationRemainingSec <= 0) { 490 onError(new KeysExpiredException()); 491 } else { 492 state = STATE_OPENED_WITH_KEYS; 493 if (eventHandler != null && eventListener != null) { 494 eventHandler.post(new Runnable() { 495 @Override 496 public void run() { 497 eventListener.onDrmKeysRestored(); 498 } 499 }); 500 } 501 } 502 } 503 } 504 break; 505 case MODE_DOWNLOAD: 506 if (offlineLicenseKeySetId == null) { 507 postKeyRequest(sessionId, MediaDrm.KEY_TYPE_OFFLINE); 508 } else { 509 // Renew 510 if (restoreKeys()) { 511 postKeyRequest(sessionId, MediaDrm.KEY_TYPE_OFFLINE); 512 } 513 } 514 break; 515 case MODE_RELEASE: 516 if (restoreKeys()) { 517 postKeyRequest(offlineLicenseKeySetId, MediaDrm.KEY_TYPE_RELEASE); 518 } 519 break; 520 } 521 } 522 restoreKeys()523 private boolean restoreKeys() { 524 try { 525 mediaDrm.restoreKeys(sessionId, offlineLicenseKeySetId); 526 return true; 527 } catch (Exception e) { 528 Log.e(TAG, "Error trying to restore Widevine keys.", e); 529 onError(e); 530 } 531 return false; 532 } 533 getLicenseDurationRemainingSec()534 private long getLicenseDurationRemainingSec() { 535 if (!C.WIDEVINE_UUID.equals(uuid)) { 536 return Long.MAX_VALUE; 537 } 538 Pair<Long, Long> pair = WidevineUtil.getLicenseDurationRemainingSec(this); 539 return Math.min(pair.first, pair.second); 540 } 541 postKeyRequest(byte[] scope, int keyType)542 private void postKeyRequest(byte[] scope, int keyType) { 543 try { 544 KeyRequest keyRequest = mediaDrm.getKeyRequest(scope, schemeInitData, schemeMimeType, keyType, 545 optionalKeyRequestParameters); 546 postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget(); 547 } catch (Exception e) { 548 onKeysError(e); 549 } 550 } 551 onKeyResponse(Object response)552 private void onKeyResponse(Object response) { 553 if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { 554 // This event is stale. 555 return; 556 } 557 558 if (response instanceof Exception) { 559 onKeysError((Exception) response); 560 return; 561 } 562 563 try { 564 if (mode == MODE_RELEASE) { 565 mediaDrm.provideKeyResponse(offlineLicenseKeySetId, (byte[]) response); 566 if (eventHandler != null && eventListener != null) { 567 eventHandler.post(new Runnable() { 568 @Override 569 public void run() { 570 eventListener.onDrmKeysRemoved(); 571 } 572 }); 573 } 574 } else { 575 byte[] keySetId = mediaDrm.provideKeyResponse(sessionId, (byte[]) response); 576 if ((mode == MODE_DOWNLOAD || (mode == MODE_PLAYBACK && offlineLicenseKeySetId != null)) 577 && keySetId != null && keySetId.length != 0) { 578 offlineLicenseKeySetId = keySetId; 579 } 580 state = STATE_OPENED_WITH_KEYS; 581 if (eventHandler != null && eventListener != null) { 582 eventHandler.post(new Runnable() { 583 @Override 584 public void run() { 585 eventListener.onDrmKeysLoaded(); 586 } 587 }); 588 } 589 } 590 } catch (Exception e) { 591 onKeysError(e); 592 } 593 } 594 onKeysError(Exception e)595 private void onKeysError(Exception e) { 596 if (e instanceof NotProvisionedException) { 597 postProvisionRequest(); 598 } else { 599 onError(e); 600 } 601 } 602 onError(final Exception e)603 private void onError(final Exception e) { 604 lastException = new DrmSessionException(e); 605 if (eventHandler != null && eventListener != null) { 606 eventHandler.post(new Runnable() { 607 @Override 608 public void run() { 609 eventListener.onDrmSessionManagerError(e); 610 } 611 }); 612 } 613 if (state != STATE_OPENED_WITH_KEYS) { 614 state = STATE_ERROR; 615 } 616 } 617 618 @SuppressLint("HandlerLeak") 619 private class MediaDrmHandler extends Handler { 620 MediaDrmHandler(Looper looper)621 public MediaDrmHandler(Looper looper) { 622 super(looper); 623 } 624 625 @SuppressWarnings("deprecation") 626 @Override handleMessage(Message msg)627 public void handleMessage(Message msg) { 628 if (openCount == 0 || (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS)) { 629 return; 630 } 631 switch (msg.what) { 632 case MediaDrm.EVENT_KEY_REQUIRED: 633 doLicense(); 634 break; 635 case MediaDrm.EVENT_KEY_EXPIRED: 636 // When an already expired key is loaded MediaDrm sends this event immediately. Ignore 637 // this event if the state isn't STATE_OPENED_WITH_KEYS yet which means we're still 638 // waiting for key response. 639 if (state == STATE_OPENED_WITH_KEYS) { 640 state = STATE_OPENED; 641 onError(new KeysExpiredException()); 642 } 643 break; 644 case MediaDrm.EVENT_PROVISION_REQUIRED: 645 state = STATE_OPENED; 646 postProvisionRequest(); 647 break; 648 } 649 } 650 651 } 652 653 private class MediaDrmEventListener implements OnEventListener<T> { 654 655 @Override onEvent(ExoMediaDrm<? extends T> md, byte[] sessionId, int event, int extra, byte[] data)656 public void onEvent(ExoMediaDrm<? extends T> md, byte[] sessionId, int event, int extra, 657 byte[] data) { 658 if (mode == MODE_PLAYBACK) { 659 mediaDrmHandler.sendEmptyMessage(event); 660 } 661 } 662 663 } 664 665 @SuppressLint("HandlerLeak") 666 private class PostResponseHandler extends Handler { 667 PostResponseHandler(Looper looper)668 public PostResponseHandler(Looper looper) { 669 super(looper); 670 } 671 672 @Override handleMessage(Message msg)673 public void handleMessage(Message msg) { 674 switch (msg.what) { 675 case MSG_PROVISION: 676 onProvisionResponse(msg.obj); 677 break; 678 case MSG_KEYS: 679 onKeyResponse(msg.obj); 680 break; 681 } 682 } 683 684 } 685 686 @SuppressLint("HandlerLeak") 687 private class PostRequestHandler extends Handler { 688 PostRequestHandler(Looper backgroundLooper)689 public PostRequestHandler(Looper backgroundLooper) { 690 super(backgroundLooper); 691 } 692 693 @Override handleMessage(Message msg)694 public void handleMessage(Message msg) { 695 Object response; 696 try { 697 switch (msg.what) { 698 case MSG_PROVISION: 699 response = callback.executeProvisionRequest(uuid, (ProvisionRequest) msg.obj); 700 break; 701 case MSG_KEYS: 702 response = callback.executeKeyRequest(uuid, (KeyRequest) msg.obj); 703 break; 704 default: 705 throw new RuntimeException(); 706 } 707 } catch (Exception e) { 708 response = e; 709 } 710 postResponseHandler.obtainMessage(msg.what, response).sendToTarget(); 711 } 712 713 } 714 715 } 716