1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 package org.mozilla.gecko.media;
6 
7 import org.mozilla.gecko.GeckoAppShell;
8 import org.mozilla.gecko.TelemetryUtils;
9 
10 import android.content.ComponentName;
11 import android.content.Context;
12 import android.content.Intent;
13 import android.content.ServiceConnection;
14 import android.media.MediaFormat;
15 import android.os.DeadObjectException;
16 import android.os.IBinder;
17 import android.os.RemoteException;
18 import android.util.Log;
19 
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.NoSuchElementException;
23 
24 import org.mozilla.gecko.gfx.GeckoSurface;
25 
26 public final class RemoteManager implements IBinder.DeathRecipient {
27     private static final String LOGTAG = "GeckoRemoteManager";
28     private static final boolean DEBUG = false;
29     private static RemoteManager sRemoteManager = null;
30 
getInstance()31     public synchronized static RemoteManager getInstance() {
32         if (sRemoteManager == null) {
33             sRemoteManager = new RemoteManager();
34         }
35 
36         sRemoteManager.init();
37         return sRemoteManager;
38     }
39 
40     private List<CodecProxy> mCodecs = new LinkedList<CodecProxy>();
41     private List<IMediaDrmBridge> mDrmBridges = new LinkedList<IMediaDrmBridge>();
42 
43     private volatile IMediaManager mRemote;
44 
45     private final class RemoteConnection implements ServiceConnection {
46         @Override
onServiceConnected(final ComponentName name, final IBinder service)47         public void onServiceConnected(final ComponentName name, final IBinder service) {
48             if (DEBUG) Log.d(LOGTAG, "service connected");
49             try {
50                 service.linkToDeath(RemoteManager.this, 0);
51             } catch (final RemoteException e) {
52                 e.printStackTrace();
53             }
54             synchronized (this) {
55                 mRemote = IMediaManager.Stub.asInterface(service);
56                 notify();
57             }
58         }
59 
60         @Override
onServiceDisconnected(final ComponentName name)61         public void onServiceDisconnected(final ComponentName name) {
62             if (DEBUG) Log.d(LOGTAG, "service disconnected");
63             unlink();
64         }
65 
connect()66         private boolean connect() {
67             final Context appCtxt = GeckoAppShell.getApplicationContext();
68             appCtxt.bindService(new Intent(appCtxt, MediaManager.class),
69                     mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
70             waitConnect();
71             return mRemote != null;
72         }
73 
74         // Wait up to 5s.
waitConnect()75         private synchronized void waitConnect() {
76             int waitCount = 0;
77             while (mRemote == null && waitCount < 5) {
78                 try {
79                     wait(1000);
80                     waitCount++;
81                 } catch (final InterruptedException e) {
82                     if (DEBUG) {
83                         e.printStackTrace();
84                     }
85                 }
86             }
87             if (DEBUG) {
88                 Log.d(LOGTAG, "wait ~" + waitCount + "s for connection: " + (mRemote == null ? "fail" : "ok"));
89             }
90         }
91 
waitDisconnect()92         private synchronized void waitDisconnect() {
93             while (mRemote != null) {
94                 try {
95                     wait(1000);
96                 } catch (final InterruptedException e) {
97                     if (DEBUG) {
98                         e.printStackTrace();
99                     }
100                 }
101             }
102         }
103 
unlink()104         private synchronized void unlink() {
105             if (mRemote == null) {
106                 return;
107             }
108             try {
109                 mRemote.asBinder().unlinkToDeath(RemoteManager.this, 0);
110             } catch (final NoSuchElementException e) {
111                 Log.w(LOGTAG, "death recipient already released");
112             }
113             mRemote = null;
114             notify();
115         }
116     };
117 
118     RemoteConnection mConnection = new RemoteConnection();
119 
init()120     private synchronized boolean init() {
121         if (mRemote != null) {
122             return true;
123         }
124 
125         if (DEBUG) Log.d(LOGTAG, "init remote manager " + this);
126         return mConnection.connect();
127     }
128 
createCodec(final boolean isEncoder, final MediaFormat format, final GeckoSurface surface, final CodecProxy.Callbacks callbacks, final String drmStubId)129     public synchronized CodecProxy createCodec(final boolean isEncoder,
130                                                final MediaFormat format,
131                                                final GeckoSurface surface,
132                                                final CodecProxy.Callbacks callbacks,
133                                                final String drmStubId) {
134         if (mRemote == null) {
135             if (DEBUG) Log.d(LOGTAG, "createCodec failed due to not initialize");
136             return null;
137         }
138         try {
139             final ICodec remote = mRemote.createCodec();
140             final CodecProxy proxy = CodecProxy.createCodecProxy(isEncoder, format, surface, callbacks, drmStubId);
141             if (proxy.init(remote)) {
142                 mCodecs.add(proxy);
143                 return proxy;
144             } else {
145                 return null;
146             }
147         } catch (final RemoteException e) {
148             e.printStackTrace();
149             return null;
150         }
151     }
152 
createRemoteMediaDrmBridge(final String keySystem, final String stubId)153     public synchronized IMediaDrmBridge createRemoteMediaDrmBridge(final String keySystem,
154                                                                    final String stubId) {
155         if (mRemote == null) {
156             if (DEBUG) Log.d(LOGTAG, "createRemoteMediaDrmBridge failed due to not initialize");
157             return null;
158         }
159         try {
160             final IMediaDrmBridge remoteBridge =
161                 mRemote.createRemoteMediaDrmBridge(keySystem, stubId);
162             mDrmBridges.add(remoteBridge);
163             return remoteBridge;
164         } catch (final RemoteException e) {
165             Log.e(LOGTAG, "Got exception during createRemoteMediaDrmBridge().", e);
166             return null;
167         }
168     }
169 
170     @Override
binderDied()171     public void binderDied() {
172         Log.e(LOGTAG, "remote codec is dead");
173         TelemetryUtils.addToHistogram("MEDIA_DECODING_PROCESS_CRASH", 1);
174         handleRemoteDeath();
175     }
176 
handleRemoteDeath()177     private synchronized void handleRemoteDeath() {
178         mConnection.waitDisconnect();
179 
180         if (init() && recoverRemoteCodec()) {
181             notifyError(false);
182         } else {
183             notifyError(true);
184         }
185     }
186 
notifyError(final boolean fatal)187     private synchronized void notifyError(final boolean fatal) {
188         for (final CodecProxy proxy : mCodecs) {
189             proxy.reportError(fatal);
190         }
191     }
192 
recoverRemoteCodec()193     private synchronized boolean recoverRemoteCodec() {
194         if (DEBUG) Log.d(LOGTAG, "recover codec");
195         boolean ok = true;
196         try {
197             for (final CodecProxy proxy : mCodecs) {
198                 ok &= proxy.init(mRemote.createCodec());
199             }
200             return ok;
201         } catch (final RemoteException e) {
202             return false;
203         }
204     }
205 
releaseCodec(final CodecProxy proxy)206     public void releaseCodec(final CodecProxy proxy)
207             throws DeadObjectException, RemoteException {
208         if (mRemote == null) {
209             if (DEBUG) Log.d(LOGTAG, "releaseCodec called but not initialized yet");
210             return;
211         }
212         proxy.deinit();
213         synchronized (this) {
214             if (mCodecs.remove(proxy)) {
215                 try {
216                     mRemote.endRequest();
217                     releaseIfNeeded();
218                 } catch (final RemoteException | NullPointerException e) {
219                     Log.e(LOGTAG, "fail to report remote codec disconnection");
220                 }
221             }
222         }
223     }
224 
releaseIfNeeded()225     private void releaseIfNeeded() {
226         if (!mCodecs.isEmpty() || !mDrmBridges.isEmpty()) {
227             return;
228         }
229 
230         if (DEBUG) Log.d(LOGTAG, "release remote manager " + this);
231         mConnection.unlink();
232         final Context appCtxt = GeckoAppShell.getApplicationContext();
233         appCtxt.unbindService(mConnection);
234     }
235 
onRemoteMediaDrmBridgeReleased(final IMediaDrmBridge remote)236     public void onRemoteMediaDrmBridgeReleased(final IMediaDrmBridge remote) {
237         if (!mDrmBridges.contains(remote)) {
238             Log.e(LOGTAG, "Try to release unknown remote MediaDrm bridge: " + remote);
239             return;
240         }
241 
242         synchronized (this) {
243             if (mDrmBridges.remove(remote)) {
244                 try {
245                     mRemote.endRequest();
246                     releaseIfNeeded();
247                 } catch (final RemoteException | NullPointerException e) {
248                     Log.e(LOGTAG, "Fail to report remote DRM bridge disconnection");
249                 }
250             }
251         }
252     }
253 } // RemoteManager
254