1// Copyright (c) 2012 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
5#include "base/bind.h"
6#include "base/bind_helpers.h"
7#include "base/memory/ref_counted.h"
8#include "base/memory/scoped_ptr.h"
9#include "base/message_loop/message_loop.h"
10#include "base/strings/string16.h"
11#include "base/time/time.h"
12#include "content/browser/geolocation/geolocation_provider_impl.h"
13#include "content/browser/geolocation/mock_location_arbitrator.h"
14#include "content/public/browser/access_token_store.h"
15#include "content/public/browser/browser_thread.h"
16#include "content/public/test/test_browser_thread.h"
17#include "testing/gmock/include/gmock/gmock.h"
18#include "testing/gtest/include/gtest/gtest.h"
19
20using testing::MakeMatcher;
21using testing::Matcher;
22using testing::MatcherInterface;
23using testing::MatchResultListener;
24
25namespace content {
26
27class LocationProviderForTestArbitrator : public GeolocationProviderImpl {
28 public:
29  LocationProviderForTestArbitrator() : mock_arbitrator_(NULL) {}
30  virtual ~LocationProviderForTestArbitrator() {}
31
32  // Only valid for use on the geolocation thread.
33  MockLocationArbitrator* mock_arbitrator() const {
34    return mock_arbitrator_;
35  }
36
37 protected:
38  // GeolocationProviderImpl implementation:
39  virtual LocationArbitrator* CreateArbitrator() OVERRIDE;
40
41 private:
42  MockLocationArbitrator* mock_arbitrator_;
43};
44
45LocationArbitrator* LocationProviderForTestArbitrator::CreateArbitrator() {
46  DCHECK(mock_arbitrator_ == NULL);
47  mock_arbitrator_ = new MockLocationArbitrator;
48  return mock_arbitrator_;
49}
50
51class GeolocationObserver {
52 public:
53  virtual ~GeolocationObserver() {}
54  virtual void OnLocationUpdate(const Geoposition& position) = 0;
55};
56
57class MockGeolocationObserver : public GeolocationObserver {
58 public:
59  MOCK_METHOD1(OnLocationUpdate, void(const Geoposition& position));
60};
61
62class AsyncMockGeolocationObserver : public MockGeolocationObserver {
63 public:
64  virtual void OnLocationUpdate(const Geoposition& position) OVERRIDE {
65    MockGeolocationObserver::OnLocationUpdate(position);
66    base::MessageLoop::current()->Quit();
67  }
68};
69
70class MockGeolocationCallbackWrapper {
71 public:
72  MOCK_METHOD1(Callback, void(const Geoposition& position));
73};
74
75class GeopositionEqMatcher
76    : public MatcherInterface<const Geoposition&> {
77 public:
78  explicit GeopositionEqMatcher(const Geoposition& expected)
79      : expected_(expected) {}
80
81  virtual bool MatchAndExplain(const Geoposition& actual,
82                               MatchResultListener* listener) const OVERRIDE {
83    return actual.latitude == expected_.latitude &&
84           actual.longitude == expected_.longitude &&
85           actual.altitude == expected_.altitude &&
86           actual.accuracy == expected_.accuracy &&
87           actual.altitude_accuracy == expected_.altitude_accuracy &&
88           actual.heading == expected_.heading &&
89           actual.speed == expected_.speed &&
90           actual.timestamp == expected_.timestamp &&
91           actual.error_code == expected_.error_code &&
92           actual.error_message == expected_.error_message;
93  }
94
95  virtual void DescribeTo(::std::ostream* os) const OVERRIDE {
96    *os << "which matches the expected position";
97  }
98
99  virtual void DescribeNegationTo(::std::ostream* os) const OVERRIDE {
100    *os << "which does not match the expected position";
101  }
102
103 private:
104  Geoposition expected_;
105
106  DISALLOW_COPY_AND_ASSIGN(GeopositionEqMatcher);
107};
108
109Matcher<const Geoposition&> GeopositionEq(const Geoposition& expected) {
110  return MakeMatcher(new GeopositionEqMatcher(expected));
111}
112
113class GeolocationProviderTest : public testing::Test {
114 protected:
115  GeolocationProviderTest()
116      : message_loop_(),
117        ui_thread_(BrowserThread::UI, &message_loop_),
118        provider_(new LocationProviderForTestArbitrator) {
119  }
120
121  virtual ~GeolocationProviderTest() {}
122
123  LocationProviderForTestArbitrator* provider() { return provider_.get(); }
124
125  // Called on test thread.
126  bool ProvidersStarted();
127  void SendMockLocation(const Geoposition& position);
128
129 private:
130  // Called on provider thread.
131  void GetProvidersStarted(bool* started);
132
133  base::MessageLoop message_loop_;
134  TestBrowserThread ui_thread_;
135  scoped_ptr<LocationProviderForTestArbitrator> provider_;
136};
137
138
139bool GeolocationProviderTest::ProvidersStarted() {
140  DCHECK(provider_->IsRunning());
141  DCHECK(base::MessageLoop::current() == &message_loop_);
142  bool started;
143  provider_->message_loop_proxy()->PostTaskAndReply(
144      FROM_HERE,
145      base::Bind(&GeolocationProviderTest::GetProvidersStarted,
146                 base::Unretained(this),
147                 &started),
148      base::MessageLoop::QuitClosure());
149  message_loop_.Run();
150  return started;
151}
152
153void GeolocationProviderTest::GetProvidersStarted(bool* started) {
154  DCHECK(base::MessageLoop::current() == provider_->message_loop());
155  *started = provider_->mock_arbitrator()->providers_started();
156}
157
158void GeolocationProviderTest::SendMockLocation(const Geoposition& position) {
159  DCHECK(provider_->IsRunning());
160  DCHECK(base::MessageLoop::current() == &message_loop_);
161  provider_->message_loop()
162      ->PostTask(FROM_HERE,
163                 base::Bind(&GeolocationProviderImpl::OnLocationUpdate,
164                            base::Unretained(provider_.get()),
165                            position));
166}
167
168// Regression test for http://crbug.com/59377
169TEST_F(GeolocationProviderTest, OnPermissionGrantedWithoutObservers) {
170  EXPECT_FALSE(provider()->user_did_opt_into_location_services_for_testing());
171  provider()->UserDidOptIntoLocationServices();
172  EXPECT_TRUE(provider()->user_did_opt_into_location_services_for_testing());
173}
174
175void DummyFunction(const Geoposition& position) {
176}
177
178TEST_F(GeolocationProviderTest, StartStop) {
179  EXPECT_FALSE(provider()->IsRunning());
180  GeolocationProviderImpl::LocationUpdateCallback callback =
181      base::Bind(&DummyFunction);
182  scoped_ptr<content::GeolocationProvider::Subscription> subscription =
183      provider()->AddLocationUpdateCallback(callback, false);
184  EXPECT_TRUE(provider()->IsRunning());
185  EXPECT_TRUE(ProvidersStarted());
186
187  subscription.reset();
188
189  EXPECT_FALSE(ProvidersStarted());
190  EXPECT_TRUE(provider()->IsRunning());
191}
192
193TEST_F(GeolocationProviderTest, StalePositionNotSent) {
194  Geoposition first_position;
195  first_position.latitude = 12;
196  first_position.longitude = 34;
197  first_position.accuracy = 56;
198  first_position.timestamp = base::Time::Now();
199
200  AsyncMockGeolocationObserver first_observer;
201  GeolocationProviderImpl::LocationUpdateCallback first_callback = base::Bind(
202      &MockGeolocationObserver::OnLocationUpdate,
203      base::Unretained(&first_observer));
204  EXPECT_CALL(first_observer, OnLocationUpdate(GeopositionEq(first_position)));
205  scoped_ptr<content::GeolocationProvider::Subscription> subscription =
206      provider()->AddLocationUpdateCallback(first_callback, false);
207  SendMockLocation(first_position);
208  base::MessageLoop::current()->Run();
209
210  subscription.reset();
211
212  Geoposition second_position;
213  second_position.latitude = 13;
214  second_position.longitude = 34;
215  second_position.accuracy = 56;
216  second_position.timestamp = base::Time::Now();
217
218  AsyncMockGeolocationObserver second_observer;
219
220  // After adding a second observer, check that no unexpected position update
221  // is sent.
222  EXPECT_CALL(second_observer, OnLocationUpdate(testing::_)).Times(0);
223  GeolocationProviderImpl::LocationUpdateCallback second_callback = base::Bind(
224      &MockGeolocationObserver::OnLocationUpdate,
225      base::Unretained(&second_observer));
226  scoped_ptr<content::GeolocationProvider::Subscription> subscription2 =
227      provider()->AddLocationUpdateCallback(second_callback, false);
228  base::MessageLoop::current()->RunUntilIdle();
229
230  // The second observer should receive the new position now.
231  EXPECT_CALL(second_observer,
232              OnLocationUpdate(GeopositionEq(second_position)));
233  SendMockLocation(second_position);
234  base::MessageLoop::current()->Run();
235
236  subscription2.reset();
237  EXPECT_FALSE(ProvidersStarted());
238}
239
240TEST_F(GeolocationProviderTest, OverrideLocationForTesting) {
241  Geoposition position;
242  position.error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
243  provider()->OverrideLocationForTesting(position);
244  // Adding an observer when the location is overridden should synchronously
245  // update the observer with our overridden position.
246  MockGeolocationObserver mock_observer;
247  EXPECT_CALL(mock_observer, OnLocationUpdate(GeopositionEq(position)));
248  GeolocationProviderImpl::LocationUpdateCallback callback = base::Bind(
249      &MockGeolocationObserver::OnLocationUpdate,
250      base::Unretained(&mock_observer));
251  scoped_ptr<content::GeolocationProvider::Subscription> subscription =
252      provider()->AddLocationUpdateCallback(callback, false);
253  subscription.reset();
254  // Wait for the providers to be stopped now that all clients are gone.
255  EXPECT_FALSE(ProvidersStarted());
256}
257
258}  // namespace content
259