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