QAndroidGmsLocationProvider.cpp 10 KB
Newer Older
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
/*
  Offscreen Android Views library for Qt

  Author:
  Vyacheslav O. Koscheev <vok1980@gmail.com>

  Distrbuted under The BSD License

  Copyright (c) 2015, DoubleGIS, LLC.
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.
  * Neither the name of the DoubleGIS, LLC nor the names of its contributors
    may be used to endorse or promote products derived from this software
    without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  THE POSSIBILITY OF SUCH DAMAGE.
*/

37
#include <algorithm>
38
#include <cassert>
39
#include "QAndroidGmsLocationProvider.h"
40 41
#include <QAndroidQPAPluginGap.h>
#include <QtPositioning/QGeoPositionInfo>
42
#include <QtGui/QGuiApplication>
43
#include <TJniObjectLinker.h>
44
#include "PositionInfoConvertor.h"
45 46


47
static const char * const c_full_class_name_ = "ru/dublgis/androidlocation/GmsLocationProvider";
48 49 50 51




52
Q_DECL_EXPORT void JNICALL Java_GooglePlayServiceLocationProvider_locationStatus(JNIEnv *, jobject, jlong param, jint state)
53
{
54
	JNI_LINKER_OBJECT(QAndroidGmsLocationProvider, param, obj)
Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
55
	obj->onStatusChanged(state);
56 57 58
}


59 60 61 62 63 64 65
Q_DECL_EXPORT void JNICALL Java_GooglePlayServiceLocationProvider_locationAvailable(JNIEnv *, jobject, jlong param, jboolean available)
{
	JNI_LINKER_OBJECT(QAndroidGmsLocationProvider, param, obj)
	obj->onLocationAvailable(available);
}


66
Q_DECL_EXPORT void JNICALL Java_GooglePlayServiceLocationProvider_locationRecieved(JNIEnv *, jobject, jlong param, jobject location, jboolean initial, jlong requestId)
67
{
68 69 70 71 72
	if (0x0 == location)
	{
		return;
	}

73
	JNI_LINKER_OBJECT(QAndroidGmsLocationProvider, param, obj)
74
	QGeoPositionInfo posInfo = positionInfoFromJavaLocation(location);
Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
75
	obj->onLocationRecieved(posInfo, initial, requestId);
76 77 78
}


79
static const JNINativeMethod methods[] = {
80 81
	{"getActivity", "()Landroid/app/Activity;", reinterpret_cast<void*>(QAndroidQPAPluginGap::getActivityNoThrow)},
	{"googleApiClientStatus", "(JI)V", reinterpret_cast<void*>(Java_GooglePlayServiceLocationProvider_locationStatus)},
82
	{"googleApiClientLocationAvailable", "(JZ)V", reinterpret_cast<void*>(Java_GooglePlayServiceLocationProvider_locationAvailable)},
83
	{"googleApiClientLocation", "(JLandroid/location/Location;ZJ)V", reinterpret_cast<void*>(Java_GooglePlayServiceLocationProvider_locationRecieved)},
84 85 86 87 88 89
};


JNI_LINKER_IMPL(QAndroidGmsLocationProvider, c_full_class_name_, methods)


Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
90 91
QAndroidGmsLocationProvider::QAndroidGmsLocationProvider(QObject * parent)
	: QObject(parent)
92
	, jniLinker_(new JniObjectLinker(this))
93 94 95 96 97
	, reqiredInterval_(1500)
	, minimumInterval_(1000)
	, priority_(PRIORITY_NO_POWER)
	, regularUpdadesId_(0)
	, requestUpdadesId_(0)
98
	, requestTimer_(this) // should be set due to parent's move to moveToThread operation
99
{
100
	QObject::connect(&requestTimer_, &QTimer::timeout,
Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
101
	                 this, &QAndroidGmsLocationProvider::onRequestTimeout);
102 103 104 105

	QObject::connect(qApp, &QGuiApplication::applicationStateChanged,
	                 this, &QAndroidGmsLocationProvider::onApplicationStateChanged);

106 107 108
	QObject::connect(this, &QAndroidGmsLocationProvider::checkRequest,
	                 this, &QAndroidGmsLocationProvider::onCheckRequest);

109
	onApplicationStateChanged(QGuiApplication::applicationState());
110 111
}

112 113

QAndroidGmsLocationProvider::~QAndroidGmsLocationProvider()
114 115 116 117
{
}


118
QGeoPositionInfo QAndroidGmsLocationProvider::lastKnownPosition() const
119 120 121 122 123 124
{
	QMutexLocker lock(&lastLocationSync_);
	return lastLocation_;
}


125 126 127 128 129 130 131
void QAndroidGmsLocationProvider::onLocationAvailable(jboolean available)
{
	emit locationAvailable(available);
}


void QAndroidGmsLocationProvider::onStatusChanged(jint status)
132
{
133 134 135 136
	const char * szStatus = "Unknown";

	switch (status)
	{
137
		case S_DISCONNECTED:
138 139
			szStatus = "Disconnected";
			break;
140
		case S_CONNECTED:
141 142
			szStatus = "Connected";
			break;
143
		case S_CONNECT_ERROR:
144 145
			szStatus = "Error";
			break;
146
		case S_CONNECT_SUSPEND:
147 148 149 150 151 152 153
			szStatus = "Suspended";
			break;
		default:
			assert(!"Unexpected status");
	}

	qDebug() << __FUNCTION__ << ": status = " << status << " (" << szStatus << ")";
154 155 156 157
	emit statusChanged(status);
}


158
void QAndroidGmsLocationProvider::onLocationRecieved(const QGeoPositionInfo &location, jboolean initial, jlong requestId)
159 160 161 162
{
	{
		QMutexLocker lock(&lastLocationSync_);
		lastLocation_ = location;
163 164
	}

165
	emit checkRequest(requestId);
166

167 168 169 170
	if (!initial)
	{
		emit locationRecieved(location);
	}
171 172 173
}


174
void QAndroidGmsLocationProvider::setPriority(enPriority priority)
175 176 177 178 179
{
	priority_ = priority;
}


180
void QAndroidGmsLocationProvider::setUpdateInterval(int reqiredInterval, int minimumInterval)
181
{
Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
182
	qInfo() << __FUNCTION__ << ": reqiredInterval" << reqiredInterval << ", minimumInterval" << minimumInterval;
183 184 185 186 187
	reqiredInterval_ = reqiredInterval;
	minimumInterval_ = minimumInterval;
}


188 189 190 191 192
void QAndroidGmsLocationProvider::startUpdates()
{
	qDebug() << __FUNCTION__;
	stopUpdates();

193
	if (isJniReady())
194
	{
195
		jlong maxWaitTime = reqiredInterval_ * 3 / 2;
196 197 198
		jint numUpdates = 0;
		jlong expirationDuration = 0;
		jlong expirationTime = 0;
199

200
		jlong id = jni()->callParamLong("startLocationUpdates", "IJJJIJJ",
201 202 203
		                                   jint(priority_),
		                                   jlong(reqiredInterval_),
		                                   jlong(minimumInterval_),
Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
204 205 206 207
		                                   maxWaitTime,
		                                   numUpdates,
		                                   expirationDuration,
		                                   expirationTime);
208 209 210 211 212

		{
			QMutexLocker lock(&lastLocationSync_);
			regularUpdadesId_ = id;
		}
Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
213

Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
214
		qDebug() << "updates id =" << id;
215 216 217 218 219 220
	}
}


void QAndroidGmsLocationProvider::stopUpdates()
{
221
	jlong id = 0;
222 223 224 225

	{
		QMutexLocker lock(&lastLocationSync_);
		id = regularUpdadesId_;
226
		regularUpdadesId_ = 0;
227 228 229 230 231 232
	}

	stopUpdates(id);
}


233
void QAndroidGmsLocationProvider::stopUpdates(jlong requestId)
234
{
235 236
	qDebug() << __FUNCTION__ << "(" << requestId << ")";

237
	if (isJniReady())
238
	{
239
		jni()->callParamVoid("stopLocationUpdates", "J", requestId);
240

241 242 243 244 245 246 247 248
		bool bStopTimer = false;

		{
			QMutexLocker lock(&lastLocationSync_);
			bStopTimer = (requestId == requestUpdadesId_);
		}

		if (bStopTimer)
249 250 251
		{
			requestTimer_.stop();
		}
252 253 254 255
	}
}


256 257
void QAndroidGmsLocationProvider::onApplicationStateChanged(Qt::ApplicationState state)
{
258
	if (isJniReady())
259 260 261 262 263 264 265
	{
		jboolean enable = (Qt::ApplicationActive == state);

		switch (state)
		{
			case Qt::ApplicationSuspended:	//onStop
			case Qt::ApplicationActive:		//onStart
266
				jni()->callParamVoid("activate", "Z", enable);
Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
267 268 269 270
				break;
			default:
				// do nothing
				break;
271 272 273 274 275
		}
	}
}


276
void QAndroidGmsLocationProvider::onCheckRequest(jlong requestId)
277 278 279 280 281 282 283 284 285 286
{
	bool stop = false;

	{
		QMutexLocker lock(&lastLocationSync_);
		stop = requestId != regularUpdadesId_;
	}

	if (stop)
	{
287
		QAndroidGmsLocationProvider::stopUpdates(requestId);
288 289 290 291
	}
}


292
void QAndroidGmsLocationProvider::onRequestTimeout()
293
{
294 295 296 297 298
	jlong id = 0;

	{
		QMutexLocker lock(&lastLocationSync_);
		id = requestUpdadesId_;
299
		requestUpdadesId_ = 0;
300 301 302
	}

	stopUpdates(id);
303 304 305 306 307 308 309
}


void QAndroidGmsLocationProvider::requestUpdate(int timeout /*= 0*/)
{
	qDebug() << __FUNCTION__;

310
	if (requestTimer_.isActive() || timeout < 0)
311 312 313 314 315 316
	{
		return;
	}

	if (0 == timeout)
	{
317
		timeout = std::max(1000, 2 * reqiredInterval_);
318 319 320 321 322
	}

	requestTimer_.setSingleShot(true);
	requestTimer_.start(timeout);

323
	if (isJniReady())
324
	{
325 326 327 328
		jlong maxWaitTime = 0;
		jint numUpdates = 1;
		jlong expirationDuration = timeout;
		jlong expirationTime = 0;
329

330
		jlong id = jni()->callParamLong("startLocationUpdates", "IJJJIJJ",
331 332 333
		                    jint(priority_),
		                    jlong(reqiredInterval_),
		                    jlong(minimumInterval_),
Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
334 335 336 337
		                    maxWaitTime,
		                    numUpdates,
		                    expirationDuration,
		                    expirationTime);
338 339 340 341 342

		{
			QMutexLocker lock(&lastLocationSync_);
			requestUpdadesId_ = id;
		}
Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
343

Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
344
		qDebug() << "request id =" << id;
345 346 347 348
	}
}


349
int QAndroidGmsLocationProvider::getGmsVersion()
350 351 352
{
	preloadJavaClasses();

Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
353
	try
354 355
	{
		QJniClass clazz(c_full_class_name_);
Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
356

357 358 359 360 361 362 363 364
		if (!clazz.jClass())
		{
			qWarning() << "Failed to instantiate: " << c_full_class_name_;
			return 0;
		}

		return clazz.callStaticParamInt("getGmsVersion", "Landroid/app/Activity;", QAndroidQPAPluginGap::Context().jObject());
	}
365
	catch (const std::exception & e)
366
	{
367
		qWarning() << "JNI exception in QAndroidGmsLocationProvider::getGmsVersion: " << e.what();
Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
368
	}
369 370 371 372 373

	return 0;
}


374
bool QAndroidGmsLocationProvider::isAvailable(jboolean allowDialog)
375 376 377
{
	preloadJavaClasses();

Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
378
	try
379
	{
380 381
		qDebug() << "Checking for Google Play Services positioning availability...";
		QJniClass clazz(c_full_class_name_);
Vyacheslav Koscheev's avatar
Vyacheslav Koscheev committed
382

383 384 385 386 387
		if (!clazz.jClass())
		{
			qWarning() << "Failed to instantiate: " << c_full_class_name_;
			return false;
		}
388
		jboolean result = clazz.callStaticParamBoolean("isAvailable", "Landroid/app/Activity;Z", QAndroidQPAPluginGap::Context().jObject(), allowDialog);
389 390
		qDebug() << "....GP positioning availability result:" << result;
		return result;
391
	}
392
	catch (const std::exception & e)
393
	{
394
		qWarning() << "JNI exception in QAndroidGmsLocationProvider::isAvailable: " << e.what();
395 396 397 398 399
	}

	return false;
}