17dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch// Copyright 2013 The Chromium Authors. All rights reserved.
27dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch// Use of this source code is governed by a BSD-style license that can be
37dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch// found in the LICENSE file.
47dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
57dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#ifndef REMOTING_CLIENT_CHROMOTING_JNI_INSTANCE_H_
67dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#define REMOTING_CLIENT_CHROMOTING_JNI_INSTANCE_H_
77dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
87dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include <string>
97dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
107dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "base/memory/ref_counted.h"
117dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "base/memory/scoped_ptr.h"
129ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch#include "base/memory/weak_ptr.h"
137dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "base/message_loop/message_loop.h"
147dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "remoting/client/chromoting_client.h"
157dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "remoting/client/client_context.h"
167dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "remoting/client/client_user_interface.h"
177dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "remoting/client/frame_consumer_proxy.h"
189ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch#include "remoting/client/jni/jni_frame_consumer.h"
19a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch#include "remoting/protocol/clipboard_stub.h"
20a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch#include "remoting/protocol/cursor_shape_stub.h"
21116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch#include "remoting/signaling/xmpp_signal_strategy.h"
227dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
237dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochnamespace remoting {
243551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
25a3f7b4e666c476898878fa745f637129375cd889Ben Murdochnamespace protocol {
263551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)class ClipboardEvent;
273551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)class CursorShapeInfo;
28a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch}  // namespace protocol
297dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
30116680a4aac90f2aa7413d9095a592090648e557Ben Murdochclass ClientStatusLogger;
315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class VideoRenderer;
32f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)class TokenFetcherProxy;
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
347dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch// ClientUserInterface that indirectly makes and receives JNI calls.
357dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochclass ChromotingJniInstance
367dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  : public ClientUserInterface,
37a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    public protocol::ClipboardStub,
38a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    public protocol::CursorShapeStub,
397dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    public base::RefCountedThreadSafe<ChromotingJniInstance> {
407dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch public:
417dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // Initiates a connection with the specified host. Call from the UI thread.
42bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch  // The instance does not take ownership of |jni_runtime|. To connect with an
43bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch  // unpaired host, pass in |pairing_id| and |pairing_secret| as empty strings.
44bbcdd45c55eb7c4641ab97aef9889b0fc828e7d3Ben Murdoch  ChromotingJniInstance(ChromotingJniRuntime* jni_runtime,
45bbcdd45c55eb7c4641ab97aef9889b0fc828e7d3Ben Murdoch                        const char* username,
46bbcdd45c55eb7c4641ab97aef9889b0fc828e7d3Ben Murdoch                        const char* auth_token,
47bbcdd45c55eb7c4641ab97aef9889b0fc828e7d3Ben Murdoch                        const char* host_jid,
48bbcdd45c55eb7c4641ab97aef9889b0fc828e7d3Ben Murdoch                        const char* host_id,
49bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch                        const char* host_pubkey,
50bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch                        const char* pairing_id,
516e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        const char* pairing_secret,
526e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                        const char* capabilities);
537dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
547dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // Terminates the current connection (if it hasn't already failed) and cleans
557dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // up. Must be called before destruction.
565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  void Disconnect();
577dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
5846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // Requests the android app to fetch a third-party token.
5946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  void FetchThirdPartyToken(
6046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      const GURL& token_url,
6146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      const std::string& client_id,
6246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      const std::string& scope,
6346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      const base::WeakPtr<TokenFetcherProxy> token_fetcher_proxy);
6446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
65f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  // Called by the android app when the token is fetched.
66f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  void HandleOnThirdPartyTokenFetched(const std::string& token,
67f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                                      const std::string& shared_secret);
68f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
697dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // Provides the user's PIN and resumes the host authentication attempt. Call
707dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // on the UI thread once the user has finished entering this PIN into the UI,
717dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // but only after the UI has been asked to provide a PIN (via FetchSecret()).
72a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  void ProvideSecret(const std::string& pin, bool create_pair,
73a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                     const std::string& device_name);
747dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
759ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch  // Schedules a redraw on the display thread. May be called from any thread.
769ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch  void RedrawDesktop();
779ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch
78a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  // Moves the host's cursor to the specified coordinates, optionally with some
79a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  // mouse button depressed. If |button| is BUTTON_UNDEFINED, no click is made.
80effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  void SendMouseEvent(int x, int y,
81effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                      protocol::MouseEvent_MouseButton button,
82effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                      bool button_down);
83effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  void SendMouseWheelEvent(int delta_x, int delta_y);
84f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
85a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // Sends the provided keyboard scan code to the host.
866d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  bool SendKeyEvent(int key_code, bool key_down);
87effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
88effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  void SendTextEvent(const std::string& text);
89a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
906e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  void SendClientMessage(const std::string& type, const std::string& data);
916e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
92f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // Records paint time for statistics logging, if enabled. May be called from
93f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // any thread.
94f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  void RecordPaintTime(int64 paint_time_ms);
95f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
967dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // ClientUserInterface implementation.
977dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  virtual void OnConnectionState(
987dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      protocol::ConnectionToHost::State state,
997dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      protocol::ErrorCode error) OVERRIDE;
1007dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  virtual void OnConnectionReady(bool ready) OVERRIDE;
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  virtual void OnRouteChanged(const std::string& channel_name,
1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                              const protocol::TransportRoute& route) OVERRIDE;
1037dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  virtual void SetCapabilities(const std::string& capabilities) OVERRIDE;
1047dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  virtual void SetPairingResponse(
1057dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      const protocol::PairingResponse& response) OVERRIDE;
106c2db58bd994c04d98e4ee2cd7565b71548655fe3Ben Murdoch  virtual void DeliverHostMessage(
107c2db58bd994c04d98e4ee2cd7565b71548655fe3Ben Murdoch      const protocol::ExtensionMessage& message) OVERRIDE;
1087dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  virtual protocol::ClipboardStub* GetClipboardStub() OVERRIDE;
1097dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  virtual protocol::CursorShapeStub* GetCursorShapeStub() OVERRIDE;
1107dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
111a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  // CursorShapeStub implementation.
112a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  virtual void InjectClipboardEvent(
113a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      const protocol::ClipboardEvent& event) OVERRIDE;
114a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
115a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  // ClipboardStub implementation.
116a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  virtual void SetCursorShape(const protocol::CursorShapeInfo& shape) OVERRIDE;
117a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
1187dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch private:
1197dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // This object is ref-counted, so it cleans itself up.
1207dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  virtual ~ChromotingJniInstance();
1217dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1227dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  void ConnectToHostOnDisplayThread();
1237dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  void ConnectToHostOnNetworkThread();
1249ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch  void DisconnectFromHostOnNetworkThread();
1257dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1267dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // Notifies the user interface that the user needs to enter a PIN. The
1277dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // current authentication attempt is put on hold until |callback| is invoked.
1289ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch  // May be called on any thread.
1297dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  void FetchSecret(bool pairable,
1307dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch                   const protocol::SecretFetchedCallback& callback);
1317dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
132a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // Sets the device name. Can be called on any thread.
133a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  void SetDeviceName(const std::string& device_name);
134a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
1356d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  void SendKeyEventInternal(int usb_key_code, bool key_down);
1366d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)
137f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // Enables or disables periodic logging of performance statistics. Called on
138f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // the network thread.
139f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  void EnableStatsLogging(bool enabled);
140f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
141f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // If logging is enabled, logs the current connection statistics, and
142f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // triggers another call to this function after the logging time interval.
143f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // Called on the network thread.
144f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  void LogPerfStats();
145f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
146bbcdd45c55eb7c4641ab97aef9889b0fc828e7d3Ben Murdoch  // Used to obtain task runner references and make calls to Java methods.
147bbcdd45c55eb7c4641ab97aef9889b0fc828e7d3Ben Murdoch  ChromotingJniRuntime* jni_runtime_;
148bbcdd45c55eb7c4641ab97aef9889b0fc828e7d3Ben Murdoch
1493551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // ID of the host we are connecting to.
1503551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  std::string host_id_;
151116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  std::string host_jid_;
1523551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
1539ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch  // This group of variables is to be used on the display thread.
1547dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  scoped_refptr<FrameConsumerProxy> frame_consumer_;
1559ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch  scoped_ptr<JniFrameConsumer> view_;
1569ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch  scoped_ptr<base::WeakPtrFactory<JniFrameConsumer> > view_weak_factory_;
1577dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1587dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // This group of variables is to be used on the network thread.
1597dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  scoped_ptr<ClientContext> client_context_;
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  scoped_ptr<VideoRenderer> video_renderer_;
161116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  scoped_ptr<protocol::Authenticator> authenticator_;
1627dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  scoped_ptr<ChromotingClient> client_;
1633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  XmppSignalStrategy::XmppServerConfig xmpp_config_;
1647dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  scoped_ptr<XmppSignalStrategy> signaling_;  // Must outlive client_
165116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  scoped_ptr<ClientStatusLogger> client_status_logger_;
166f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  base::WeakPtr<TokenFetcherProxy> token_fetcher_proxy_;
1677dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1687dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // Pass this the user's PIN once we have it. To be assigned and accessed on
1697dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  // the UI thread, but must be posted to the network thread to call it.
1707dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  protocol::SecretFetchedCallback pin_callback_;
1717dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
172bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch  // Indicates whether to establish a new pairing with this host. This is
173bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch  // modified in ProvideSecret(), but thereafter to be used only from the
174bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch  // network thread. (This is safe because ProvideSecret() is invoked at most
175bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch  // once per run, and always before any reference to this flag.)
176bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch  bool create_pairing_;
1777dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
178a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // The device name to appear in the paired-clients list. Accessed on the
179a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // network thread.
180a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  std::string device_name_;
181a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
182f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // If this is true, performance statistics will be periodically written to
183f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // the Android log. Used on the network thread.
184f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  bool stats_logging_enabled_;
185f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1866e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  // The set of capabilities supported by the client. Accessed on the network
1876e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  // thread. Once SetCapabilities() is called, this will contain the negotiated
1886e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  // set of capabilities for this remoting session.
1896e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  std::string capabilities_;
1906e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1917dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  friend class base::RefCountedThreadSafe<ChromotingJniInstance>;
1927dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
193f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  base::WeakPtrFactory<ChromotingJniInstance> weak_factory_;
194f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
1957dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  DISALLOW_COPY_AND_ASSIGN(ChromotingJniInstance);
1967dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch};
1977dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1987dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch}  // namespace remoting
1997dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
2007dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#endif
201