// 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.chromoting; import android.app.Activity; import android.text.TextUtils; import org.chromium.base.Log; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * A manager for the capabilities of the Android client. Based on the negotiated set of * capabilities, it creates the associated ClientExtensions, and enables their communication with * the Chromoting host by dispatching extension messages appropriately. * * The CapabilityManager mirrors how the Chromoting host handles extension messages. For each * incoming extension message, runs through a list of HostExtensionSession objects, giving each one * a chance to handle the message. */ public class CapabilityManager { /** Used to allow objects to receive notifications when the host capabilites are received. */ public interface CapabilitiesChangedListener { void onCapabilitiesChanged(List newCapabilities); } /** Tracks whether the remote host supports a capability. */ public enum HostCapability { UNKNOWN, SUPPORTED, UNSUPPORTED; public boolean isSet() { return this != UNKNOWN; } public boolean isSupported() { assert isSet(); return this == SUPPORTED; } } private static final String TAG = "Chromoting"; /** List of all capabilities that are supported by the application. */ private List mLocalCapabilities; /** List of negotiated capabilities received from the host. */ private List mNegotiatedCapabilities; /** List of extensions to the client based on capabilities negotiated with the host. */ private List mClientExtensions; /** Maintains a list of listeners to notify when host capabilities are received. */ private List mCapabilitiesChangedListeners; public CapabilityManager() { mLocalCapabilities = new ArrayList(); mClientExtensions = new ArrayList(); mLocalCapabilities.add(Capabilities.CAST_CAPABILITY); mLocalCapabilities.add(Capabilities.TOUCH_CAPABILITY); mCapabilitiesChangedListeners = new ArrayList(); } /** * Cleans up host specific state when the connection has been terminated. */ public void onHostDisconnect() { mNegotiatedCapabilities = null; } /** * Returns a space-separated list (required by host) of the capabilities supported by * this client. */ public String getLocalCapabilities() { return TextUtils.join(" ", mLocalCapabilities); } /** * Registers the given listener object so it is notified when host capabilities are negotiated. */ public void addListener(CapabilitiesChangedListener listener) { assert !mCapabilitiesChangedListeners.contains(listener); mCapabilitiesChangedListeners.add(listener); // If we have already received the host capabilities before this listener was registered, // then fire the event for this listener immediately. if (mNegotiatedCapabilities != null) { // Clone the capabilities list passed to the caller to prevent them from mutating it. listener.onCapabilitiesChanged(new ArrayList<>(mNegotiatedCapabilities)); } } /** * Removes the given listener object from the list of change listeners. */ public void removeListener(CapabilitiesChangedListener listener) { assert mCapabilitiesChangedListeners.contains(listener); mCapabilitiesChangedListeners.remove(listener); } /** * Returns the ActivityLifecycleListener associated with the specified capability, if * |capability| is enabled and such a listener exists. * * Activities that call this method agree to appropriately notify the listener of lifecycle * events., thus supporting |capability|. This allows extensions like the CastExtensionHandler * to hook into an existing activity's lifecycle. */ public ActivityLifecycleListener onActivityAcceptingListener( Activity activity, String capability) { ActivityLifecycleListener listener; if (isCapabilityEnabled(capability)) { for (ClientExtension ext : mClientExtensions) { if (ext.getCapability().equals(capability)) { listener = ext.onActivityAcceptingListener(activity); if (listener != null) return listener; } } } return new DummyActivityLifecycleListener(); } /** * Receives the capabilities negotiated between client and host, creates the appropriate * extension handlers, and notifies registered listeners of the change. */ public void setNegotiatedCapabilities(String capabilities) { mNegotiatedCapabilities = Arrays.asList(capabilities.split(" ")); mClientExtensions.clear(); if (isCapabilityEnabled(Capabilities.CAST_CAPABILITY)) { mClientExtensions.add(maybeCreateCastExtensionHandler()); } // Clone the list of listeners to prevent problems if the callback calls back into this // object and removes itself from the list of listeners. List listeners = new ArrayList<>(mCapabilitiesChangedListeners); for (CapabilitiesChangedListener listener : listeners) { // Clone the capabilities list passed to the caller to prevent them from mutating it. listener.onCapabilitiesChanged(new ArrayList<>(mNegotiatedCapabilities)); } } /** * Passes the deconstructed extension message to each ClientExtension in turn until the message * is handled or none remain. Returns true if the message was handled. */ public boolean onExtensionMessage(String type, String data) { if (type == null || type.isEmpty()) { return false; } for (ClientExtension ext : mClientExtensions) { if (ext.onExtensionMessage(type, data)) { return true; } } return false; } /** * Return true if the capability is enabled for this connection with the host. */ private boolean isCapabilityEnabled(String capability) { return (mNegotiatedCapabilities != null && mNegotiatedCapabilities.contains(capability)); } /** * Tries to reflectively instantiate a CastExtensionHandler object. * * Note: The ONLY reason this is done is that by default, the regular android application * will be built, without this experimental extension. */ private ClientExtension maybeCreateCastExtensionHandler() { try { Class cls = Class.forName("org.chromium.chromoting.CastExtensionHandler"); return (ClientExtension) cls.newInstance(); } catch (ClassNotFoundException e) { Log.w(TAG, "Failed to create CastExtensionHandler."); return new DummyClientExtension(); } catch (InstantiationException e) { Log.w(TAG, "Failed to create CastExtensionHandler."); return new DummyClientExtension(); } catch (IllegalAccessException e) { Log.w(TAG, "Failed to create CastExtensionHandler."); return new DummyClientExtension(); } } }