1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.chromoting;
6
7import android.app.Activity;
8import android.text.TextUtils;
9import android.util.Log;
10
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.List;
14
15/**
16 * A manager for the capabilities of the Android client. Based on the negotiated set of
17 * capabilities, it creates the associated ClientExtensions, and enables their communication with
18 * the Chromoting host by dispatching extension messages appropriately.
19 *
20 * The CapabilityManager mirrors how the Chromoting host handles extension messages. For each
21 * incoming extension message, runs through a list of HostExtensionSession objects, giving each one
22 * a chance to handle the message.
23 *
24 * The CapabilityManager is a singleton class so we can manage client extensions on an application
25 * level. The singleton object may be used from multiple Activities, thus allowing it to support
26 * different capabilities at different stages of the application.
27 */
28public class CapabilityManager {
29
30    /** Lazily-initialized singleton object that can be used from different Activities. */
31    private static CapabilityManager sInstance;
32
33    /** Protects access to |sInstance|. */
34    private static final Object sInstanceLock = new Object();
35
36    /** List of all capabilities that are supported by the application. */
37    private List<String> mLocalCapabilities;
38
39    /** List of negotiated capabilities received from the host. */
40    private List<String> mNegotiatedCapabilities;
41
42    /** List of extensions to the client based on capabilities negotiated with the host. */
43    private List<ClientExtension> mClientExtensions;
44
45    private CapabilityManager() {
46        mLocalCapabilities = new ArrayList<String>();
47        mClientExtensions = new ArrayList<ClientExtension>();
48
49        mLocalCapabilities.add(Capabilities.CAST_CAPABILITY);
50    }
51
52    /**
53     * Returns the singleton object. Thread-safe.
54     */
55    public static CapabilityManager getInstance() {
56        synchronized (sInstanceLock) {
57            if (sInstance == null) {
58                sInstance = new CapabilityManager();
59            }
60            return sInstance;
61        }
62    }
63
64    /**
65     * Returns a space-separated list (required by host) of the capabilities supported by
66     * this client.
67     */
68    public String getLocalCapabilities() {
69        return TextUtils.join(" ", mLocalCapabilities);
70    }
71
72    /**
73     * Returns the ActivityLifecycleListener associated with the specified capability, if
74     * |capability| is enabled and such a listener exists.
75     *
76     * Activities that call this method agree to appropriately notify the listener of lifecycle
77     * events., thus supporting |capability|. This allows extensions like the CastExtensionHandler
78     * to hook into an existing activity's lifecycle.
79     */
80    public ActivityLifecycleListener onActivityAcceptingListener(
81            Activity activity, String capability) {
82
83        ActivityLifecycleListener listener;
84
85        if (isCapabilityEnabled(capability)) {
86            for (ClientExtension ext : mClientExtensions) {
87                if (ext.getCapability().equals(capability)) {
88                    listener = ext.onActivityAcceptingListener(activity);
89                    if (listener != null)
90                        return listener;
91                }
92            }
93        }
94
95        return new DummyActivityLifecycleListener();
96    }
97
98    /**
99     * Receives the capabilities negotiated between client and host and creates the appropriate
100     * extension handlers.
101     *
102     * Currently only the CAST_CAPABILITY exists, so that is the only extension constructed.
103     */
104    public void setNegotiatedCapabilities(String capabilities) {
105        mNegotiatedCapabilities = Arrays.asList(capabilities.split(" "));
106        mClientExtensions.clear();
107        if (isCapabilityEnabled(Capabilities.CAST_CAPABILITY)) {
108            mClientExtensions.add(maybeCreateCastExtensionHandler());
109        }
110    }
111
112    /**
113     * Passes the deconstructed extension message to each ClientExtension in turn until the message
114     * is handled or none remain. Returns true if the message was handled.
115     */
116    public boolean onExtensionMessage(String type, String data) {
117        if (type == null || type.isEmpty()) {
118            return false;
119        }
120
121        for (ClientExtension ext : mClientExtensions) {
122            if (ext.onExtensionMessage(type, data)) {
123                return true;
124            }
125        }
126        return false;
127    }
128
129    /**
130     * Return true if the capability is enabled for this connection with the host.
131     */
132    private boolean isCapabilityEnabled(String capability) {
133        return (mNegotiatedCapabilities != null && mNegotiatedCapabilities.contains(capability));
134    }
135
136    /**
137     * Tries to reflectively instantiate a CastExtensionHandler object.
138     *
139     * Note: The ONLY reason this is done is that by default, the regular android application
140     * will be built, without this experimental extension.
141     */
142    private ClientExtension maybeCreateCastExtensionHandler() {
143        try {
144            Class<?> cls = Class.forName("org.chromium.chromoting.CastExtensionHandler");
145            return (ClientExtension) cls.newInstance();
146        } catch (ClassNotFoundException e) {
147            Log.w("CapabilityManager", "Failed to create CastExtensionHandler.");
148            return new DummyClientExtension();
149        } catch (InstantiationException e) {
150            Log.w("CapabilityManager", "Failed to create CastExtensionHandler.");
151            return new DummyClientExtension();
152        } catch (IllegalAccessException e) {
153            Log.w("CapabilityManager", "Failed to create CastExtensionHandler.");
154            return new DummyClientExtension();
155        }
156    }
157}
158