| Index: components/devtools_bridge/android/java/src/org/chromium/components/devtools_bridge/DevToolsBridgeServer.java
|
| diff --git a/components/devtools_bridge/android/java/src/org/chromium/components/devtools_bridge/DevToolsBridgeServer.java b/components/devtools_bridge/android/java/src/org/chromium/components/devtools_bridge/DevToolsBridgeServer.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..45e2a092c2ef9542606a2c47ef12a93d078ac930
|
| --- /dev/null
|
| +++ b/components/devtools_bridge/android/java/src/org/chromium/components/devtools_bridge/DevToolsBridgeServer.java
|
| @@ -0,0 +1,228 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.components.devtools_bridge;
|
| +
|
| +import android.app.Service;
|
| +import android.content.Context;
|
| +import android.content.Intent;
|
| +import android.os.PowerManager;
|
| +
|
| +import org.chromium.components.devtools_bridge.ui.ServiceUIFactory;
|
| +import org.chromium.components.devtools_bridge.util.LooperExecutor;
|
| +
|
| +import java.util.HashMap;
|
| +import java.util.List;
|
| +import java.util.Map;
|
| +
|
| +/**
|
| + * Android service mixin implementing DevTools Bridge features that not depend on
|
| + * WebRTC signaling. Ability to host this class in different service classes allows:
|
| + * 1. Parametrization.
|
| + * 2. Simplified signaling for tests.
|
| + *
|
| + * Service starts foreground once any remote client starts a debugging session. Stops when all
|
| + * remote clients disconnect.
|
| + *
|
| + * Must be called on service's main thread.
|
| + */
|
| +public class DevToolsBridgeServer implements SignalingReceiver {
|
| + public final int NOTIFICATION_ID = 1;
|
| + public final String DISCONNECT_ALL_CLIENTS_ACTION =
|
| + "action.DISCONNECT_ALL_CLIENTS_ACTION";
|
| +
|
| + public final String WAKELOCK_KEY = "wake_lock.DevToolsBridgeServer";
|
| +
|
| + private final Service mHost;
|
| + private final String mSocketName;
|
| + private final ServiceUIFactory mServiceUIFactory;
|
| + private final LooperExecutor mExecutor;
|
| + private final SessionDependencyFactory mFactory = new SessionDependencyFactory();
|
| + private final Map<String, ServerSession> mSessions = new HashMap<String, ServerSession>();
|
| + private PowerManager.WakeLock mWakeLock;
|
| + private Runnable mForegroundCompletionCallback;
|
| +
|
| + public DevToolsBridgeServer(Service host, String socketName, ServiceUIFactory uiFactory) {
|
| + mHost = host;
|
| + mSocketName = socketName;
|
| + mServiceUIFactory = uiFactory;
|
| + mExecutor = LooperExecutor.newInstanceForMainLooper(mHost);
|
| +
|
| + checkCalledOnHostServiceThread();
|
| + }
|
| +
|
| + private void checkCalledOnHostServiceThread() {
|
| + assert mExecutor.isCalledOnSessionThread();
|
| + }
|
| +
|
| + public Service getContext() {
|
| + return mHost;
|
| + }
|
| +
|
| + /**
|
| + * Should be called in service's onStartCommand. If it can handle then the method should
|
| + * delegate the work to the server.
|
| + */
|
| + public boolean canHandle(Intent intent) {
|
| + String action = intent.getAction();
|
| + return DISCONNECT_ALL_CLIENTS_ACTION.equals(action);
|
| + }
|
| +
|
| + public int onStartCommand(Intent intent) {
|
| + assert canHandle(intent);
|
| + String action = intent.getAction();
|
| + if (DISCONNECT_ALL_CLIENTS_ACTION.equals(action)) {
|
| + closeAllSessions();
|
| + }
|
| + return Service.START_NOT_STICKY;
|
| + }
|
| +
|
| + /**
|
| + * Should be called in service's onDestroy.
|
| + */
|
| + public void dispose() {
|
| + checkCalledOnHostServiceThread();
|
| +
|
| + for (ServerSession session : mSessions.values()) {
|
| + session.dispose();
|
| + }
|
| + mFactory.dispose();
|
| + }
|
| +
|
| + @Override
|
| + public void startSession(
|
| + String sessionId,
|
| + RTCConfiguration config,
|
| + String offer,
|
| + SessionBase.NegotiationCallback callback) {
|
| + checkCalledOnHostServiceThread();
|
| + if (mSessions.containsKey(sessionId)) {
|
| + callback.onFailure("Session already exists");
|
| + return;
|
| + }
|
| +
|
| + ServerSession session = new ServerSession(mFactory, mExecutor, mSocketName);
|
| + session.setEventListener(new SessionEventListener(sessionId));
|
| + mSessions.put(sessionId, session);
|
| + session.startSession(config, offer, callback);
|
| + if (mSessions.size() == 1)
|
| + startForeground();
|
| + }
|
| +
|
| + @Override
|
| + public void renegotiate(
|
| + String sessionId,
|
| + String offer,
|
| + SessionBase.NegotiationCallback callback) {
|
| + checkCalledOnHostServiceThread();
|
| + if (!mSessions.containsKey(sessionId)) {
|
| + callback.onFailure("Session does not exist");
|
| + return;
|
| + }
|
| + ServerSession session = mSessions.get(sessionId);
|
| + session.renegotiate(offer, callback);
|
| + }
|
| +
|
| + @Override
|
| + public void iceExchange(
|
| + String sessionId,
|
| + List<String> clientCandidates,
|
| + SessionBase.IceExchangeCallback callback) {
|
| + checkCalledOnHostServiceThread();
|
| + if (!mSessions.containsKey(sessionId)) {
|
| + callback.onFailure("Session does not exist");
|
| + return;
|
| + }
|
| + ServerSession session = mSessions.get(sessionId);
|
| + session.iceExchange(clientCandidates, callback);
|
| + }
|
| +
|
| + protected void startForeground() {
|
| + mForegroundCompletionCallback = startSticky();
|
| + checkCalledOnHostServiceThread();
|
| + mHost.startForeground(
|
| + NOTIFICATION_ID,
|
| + mServiceUIFactory.newForegroundNotification(mHost, DISCONNECT_ALL_CLIENTS_ACTION));
|
| + }
|
| +
|
| + protected void stopForeground() {
|
| + checkCalledOnHostServiceThread();
|
| + mHost.stopForeground(true);
|
| + mForegroundCompletionCallback.run();
|
| + mForegroundCompletionCallback = null;
|
| + }
|
| +
|
| + public void postOnServiceThread(Runnable runnable) {
|
| + mExecutor.postOnSessionThread(0, runnable);
|
| + }
|
| +
|
| + private class SessionEventListener implements SessionBase.EventListener {
|
| + private final String mSessionId;
|
| +
|
| + public SessionEventListener(String sessionId) {
|
| + mSessionId = sessionId;
|
| + }
|
| +
|
| + public void onCloseSelf() {
|
| + checkCalledOnHostServiceThread();
|
| +
|
| + mSessions.remove(mSessionId);
|
| + if (mSessions.size() == 0) {
|
| + stopForeground();
|
| + }
|
| + }
|
| + }
|
| +
|
| + private void closeAllSessions() {
|
| + if (mSessions.isEmpty()) return;
|
| + for (ServerSession session : mSessions.values()) {
|
| + session.stop();
|
| + }
|
| + mSessions.clear();
|
| + stopForeground();
|
| + }
|
| +
|
| + /**
|
| + * Helper method for doing background tasks. Usage:
|
| + *
|
| + * int onStartCommand(...) {
|
| + * if (..*) {
|
| + * startWorkInBackground(startSticky());
|
| + * return START_STICKY;
|
| + * }
|
| + * ...
|
| + * }
|
| + *
|
| + * void doWorkInBackground(final Runable completionHandler) {
|
| + * ... start background task
|
| + * @Override
|
| + * void run() {
|
| + * ...
|
| + * completionHandler.run();
|
| + * }
|
| + * }
|
| + */
|
| + public Runnable startSticky() {
|
| + checkCalledOnHostServiceThread();
|
| + if (mWakeLock == null) {
|
| + PowerManager pm = (PowerManager) mHost.getSystemService(Context.POWER_SERVICE);
|
| + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
|
| + }
|
| + mWakeLock.acquire();
|
| + return new StartStickyCompletionHandler();
|
| + }
|
| +
|
| + private class StartStickyCompletionHandler implements Runnable {
|
| + @Override
|
| + public void run() {
|
| + postOnServiceThread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mWakeLock.release();
|
| + if (!mWakeLock.isHeld()) mHost.stopSelf();
|
| + }
|
| + });
|
| + }
|
| + }
|
| +}
|
|
|