/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.media; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.media.MediaFormat; import android.os.DeadObjectException; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.TelemetryUtils; import org.mozilla.gecko.gfx.GeckoSurface; public final class RemoteManager implements IBinder.DeathRecipient { private static final String LOGTAG = "GeckoRemoteManager"; private static final boolean DEBUG = false; private static RemoteManager sRemoteManager = null; public static synchronized RemoteManager getInstance() { if (sRemoteManager == null) { sRemoteManager = new RemoteManager(); } sRemoteManager.init(); return sRemoteManager; } private List mCodecs = new LinkedList(); private List mDrmBridges = new LinkedList(); private volatile IMediaManager mRemote; private final class RemoteConnection implements ServiceConnection { @Override public void onServiceConnected(final ComponentName name, final IBinder service) { if (DEBUG) Log.d(LOGTAG, "service connected"); try { service.linkToDeath(RemoteManager.this, 0); } catch (final RemoteException e) { e.printStackTrace(); } synchronized (this) { mRemote = IMediaManager.Stub.asInterface(service); notify(); } } @Override public void onServiceDisconnected(final ComponentName name) { if (DEBUG) Log.d(LOGTAG, "service disconnected"); unlink(); } private boolean connect() { final Context appCtxt = GeckoAppShell.getApplicationContext(); appCtxt.bindService( new Intent(appCtxt, MediaManager.class), mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT); waitConnect(); return mRemote != null; } // Wait up to 5s. private synchronized void waitConnect() { int waitCount = 0; while (mRemote == null && waitCount < 5) { try { wait(1000); waitCount++; } catch (final InterruptedException e) { if (DEBUG) { e.printStackTrace(); } } } if (DEBUG) { Log.d( LOGTAG, "wait ~" + waitCount + "s for connection: " + (mRemote == null ? "fail" : "ok")); } } private synchronized void waitDisconnect() { while (mRemote != null) { try { wait(1000); } catch (final InterruptedException e) { if (DEBUG) { e.printStackTrace(); } } } } private synchronized void unlink() { if (mRemote == null) { return; } try { mRemote.asBinder().unlinkToDeath(RemoteManager.this, 0); } catch (final NoSuchElementException e) { Log.w(LOGTAG, "death recipient already released"); } mRemote = null; notify(); } } RemoteConnection mConnection = new RemoteConnection(); private synchronized boolean init() { if (mRemote != null) { return true; } if (DEBUG) Log.d(LOGTAG, "init remote manager " + this); return mConnection.connect(); } public synchronized CodecProxy createCodec( final boolean isEncoder, final MediaFormat format, final GeckoSurface surface, final CodecProxy.Callbacks callbacks, final String drmStubId) { if (mRemote == null) { if (DEBUG) Log.d(LOGTAG, "createCodec failed due to not initialize"); return null; } try { final ICodec remote = mRemote.createCodec(); final CodecProxy proxy = CodecProxy.createCodecProxy(isEncoder, format, surface, callbacks, drmStubId); if (proxy.init(remote)) { mCodecs.add(proxy); return proxy; } else { return null; } } catch (final RemoteException e) { e.printStackTrace(); return null; } } public synchronized IMediaDrmBridge createRemoteMediaDrmBridge( final String keySystem, final String stubId) { if (mRemote == null) { if (DEBUG) Log.d(LOGTAG, "createRemoteMediaDrmBridge failed due to not initialize"); return null; } try { final IMediaDrmBridge remoteBridge = mRemote.createRemoteMediaDrmBridge(keySystem, stubId); mDrmBridges.add(remoteBridge); return remoteBridge; } catch (final RemoteException e) { Log.e(LOGTAG, "Got exception during createRemoteMediaDrmBridge().", e); return null; } } @Override public void binderDied() { Log.e(LOGTAG, "remote codec is dead"); TelemetryUtils.addToHistogram("MEDIA_DECODING_PROCESS_CRASH", 1); handleRemoteDeath(); } private synchronized void handleRemoteDeath() { mConnection.waitDisconnect(); if (init() && recoverRemoteCodec()) { notifyError(false); } else { notifyError(true); } } private synchronized void notifyError(final boolean fatal) { for (final CodecProxy proxy : mCodecs) { proxy.reportError(fatal); } } private synchronized boolean recoverRemoteCodec() { if (DEBUG) Log.d(LOGTAG, "recover codec"); boolean ok = true; try { for (final CodecProxy proxy : mCodecs) { ok &= proxy.init(mRemote.createCodec()); } return ok; } catch (final RemoteException e) { return false; } } public void releaseCodec(final CodecProxy proxy) throws DeadObjectException, RemoteException { if (mRemote == null) { if (DEBUG) Log.d(LOGTAG, "releaseCodec called but not initialized yet"); return; } proxy.deinit(); synchronized (this) { if (mCodecs.remove(proxy)) { try { mRemote.endRequest(); releaseIfNeeded(); } catch (final RemoteException | NullPointerException e) { Log.e(LOGTAG, "fail to report remote codec disconnection"); } } } } private void releaseIfNeeded() { if (!mCodecs.isEmpty() || !mDrmBridges.isEmpty()) { return; } if (DEBUG) Log.d(LOGTAG, "release remote manager " + this); mConnection.unlink(); final Context appCtxt = GeckoAppShell.getApplicationContext(); appCtxt.unbindService(mConnection); } public void onRemoteMediaDrmBridgeReleased(final IMediaDrmBridge remote) { if (!mDrmBridges.contains(remote)) { Log.e(LOGTAG, "Try to release unknown remote MediaDrm bridge: " + remote); return; } synchronized (this) { if (mDrmBridges.remove(remote)) { try { mRemote.endRequest(); releaseIfNeeded(); } catch (final RemoteException | NullPointerException e) { Log.e(LOGTAG, "Fail to report remote DRM bridge disconnection"); } } } } } // RemoteManager