1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
// 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<String> 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<String> mLocalCapabilities;
/** List of negotiated capabilities received from the host. */
private List<String> mNegotiatedCapabilities;
/** List of extensions to the client based on capabilities negotiated with the host. */
private List<ClientExtension> mClientExtensions;
/** Maintains a list of listeners to notify when host capabilities are received. */
private List<CapabilitiesChangedListener> mCapabilitiesChangedListeners;
public CapabilityManager() {
mLocalCapabilities = new ArrayList<String>();
mClientExtensions = new ArrayList<ClientExtension>();
mLocalCapabilities.add(Capabilities.CAST_CAPABILITY);
mLocalCapabilities.add(Capabilities.TOUCH_CAPABILITY);
mCapabilitiesChangedListeners = new ArrayList<CapabilitiesChangedListener>();
}
/**
* 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<CapabilitiesChangedListener> 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();
}
}
}
|