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