Commit a0fcccc4 authored by Vyacheslav Koscheev's avatar Vyacheslav Koscheev

GLPv3+cell support

parent ba663d28
......@@ -41,53 +41,30 @@
namespace Mobility {
CellData::CellData()
{
clear();
}
bool CellData::compare( const CellData& other, bool compareSignalStrength ) const
{
if( cellId != other.cellId ||
locationAreaCode != other.locationAreaCode ||
mobileCountryCode != other.mobileCountryCode ||
mobileNetworkCode != other.mobileNetworkCode ||
timingAdvance != other.timingAdvance )
{
return false;
}
if( compareSignalStrength &&
signalStrength != other.signalStrength )
{
return false;
}
return true;
}
bool CellData::operator==(const CellData &other) const
{
return compare(other, true);
}
const int32_t CellData::java_integer_max_value = std::numeric_limits<int32_t>::max();
bool CellData::operator!=(const CellData &other) const
CellData::Data::Data(int32_t cell_id)
: cell_id_(cell_id)
, location_area_code_(java_integer_max_value)
, mobile_country_code_(java_integer_max_value)
, mobile_network_code_(java_integer_max_value)
, signal_strength_(java_integer_max_value)
, timing_advance_(java_integer_max_value)
{
return !compare(other, true);
}
// used as sign of validity
bool CellData::isEmpty() const
void CellData::Data::accept(DataOperation & operation) const
{
return !(cellId && locationAreaCode && mobileCountryCode && mobileNetworkCode);
operation.execute(QStringLiteral("cell_id"), cell_id_);
operation.execute(QStringLiteral("location_area_code"), location_area_code_);
operation.execute(QStringLiteral("mobile_country_code"), mobile_country_code_);
operation.execute(QStringLiteral("mobile_network_code"), mobile_network_code_);
operation.execute(QStringLiteral("signal_strength"), signal_strength_);
operation.execute(QStringLiteral("timing_advance"), timing_advance_);
operation.execute(QStringLiteral("radio_type"), radio_type_);
}
void CellData::clear()
{
memset(this, 0, sizeof(*this));
}
}
} // namespace Mobility
......@@ -45,39 +45,47 @@ namespace Mobility {
struct CellData
{
CellData();
bool compare( const CellData& other, bool compareSignalStrength ) const;
bool operator==(const CellData& other) const;
bool operator!=(const CellData& other) const;
bool isEmpty() const; // used as sign of validity
void clear();
// fields are described in http://code.google.com/intl/ru/apis/gears/geolocation_network_protocol.html
// Unique identifier of the cell. (CID for GSM, BID for CDMA)
int cellId;
// Location Area Code (LAC for GSM, NID for CDMA)
int locationAreaCode;
// Mobile Country Code (MCC for GSM and CDMA)
int mobileCountryCode;
// Mobile Network Code (MNC for GSM, SID for CDMA)
int mobileNetworkCode;
// Radio signal strength measured in dBm.
/* convert GSM asu (TS 27.007 8.5) to these as follows:
<rssi>:
0 -113 dBm or less
1 -111 dBm
2...30 -109... -53 dBm
31 -51 dBm or greater
99 not known or not detectable
*/
int signalStrength;
// Represents the distance from the cell tower. Each unit is roughly 550 meters.
int timingAdvance;
struct DataOperation
{
virtual void execute(const QString & key, int32_t value) = 0;
virtual void execute(const QString & key, const QString & value) = 0;
};
struct Data
{
Data(int32_t cell_id);
void accept(DataOperation & operation) const;
// required, int.
// Cell ID (CID) for GSM
// Base Station ID (BID) for CDMA
// UTRAN/GERAN Cell Identity (UC-Id) for WCDMA.
int32_t cell_id_;
// optional, int.
// Location Area Code (LAC) for GSM and WCDMA.
// Network ID (NID) for CDMA сетей. 0 <= LAC <= 65535.
int32_t location_area_code_;
// optional, int. 0 <= MCC < 1000
int32_t mobile_country_code_;
// optional, int. 0 <= MNC < 1000
int32_t mobile_network_code_;
// optional, int. RSSI в dBm.
int32_t signal_strength_;
// optional, int
int32_t timing_advance_;
// gsm, wcdma, lte, cdma
QString radio_type_;
};
typedef QList<Data> DataColl;
DataColl data_;
static const int32_t java_integer_max_value;
};
......
......@@ -37,23 +37,33 @@
*/
#include "QAndroidCellDataProvider.h"
#include <QtCore/QtDebug>
#include <QAndroidQPAPluginGap.h>
#include "TJniObjectLinker.h"
namespace Mobility {
Q_DECL_EXPORT void JNICALL Java_CellListener_cellUpdate(JNIEnv *, jobject, jlong native_ptr, jint cid, jint lac, jint mcc, jint mnc, jint rssi)
Q_DECL_EXPORT void JNICALL Java_CellListener_cellUpdate(JNIEnv *, jobject, jlong native_ptr, jstring type, jint cid, jint lac, jint mcc, jint mnc, jint rssi, jint ta)
{
JNI_LINKER_OBJECT(Mobility::QAndroidCellDataProvider, native_ptr, proxy)
proxy->cellUpdate(type, cid, lac, mcc, mnc, rssi, ta);
}
Q_DECL_EXPORT void JNICALL Java_CellListener_onSignalChanged(JNIEnv *, jobject, jlong native_ptr)
{
JNI_LINKER_OBJECT(Mobility::QAndroidCellDataProvider, native_ptr, proxy)
proxy->cellUpdate(cid, lac, mcc, mnc, rssi);
proxy->onSignalChanged();
}
static const JNINativeMethod methods[] = {
{"getContext", "()Landroid/content/Context;", reinterpret_cast<void*>(QAndroidQPAPluginGap::getCurrentContextNoThrow)},
// private native void cellUpdate(long native_ptr, int cid, int lac, int mcc, int mnc, int rssi);
{"cellUpdate", "(JIIIII)V", reinterpret_cast<void*>(Java_CellListener_cellUpdate)},
{"onSignalChanged", "(J)V", reinterpret_cast<void*>(Java_CellListener_onSignalChanged)},
{"cellUpdate", "(JLjava/lang/String;IIIIII)V", reinterpret_cast<void*>(Java_CellListener_cellUpdate)},
};
......@@ -74,48 +84,92 @@ QAndroidCellDataProvider::~QAndroidCellDataProvider()
void QAndroidCellDataProvider::start()
{
try
{
if (isJniReady())
{
jni()->callBool("start");
}
}
catch (const std::exception & ex)
{
qCritical() << "JNI exception in QAndroidCellDataProvider:" << ex.what();
}
}
void QAndroidCellDataProvider::stop()
{
try
{
if (isJniReady())
{
jni()->callVoid("stop");
}
}
catch (const std::exception & ex)
{
qCritical() << "JNI exception in QAndroidCellDataProvider:" << ex.what();
}
}
void QAndroidCellDataProvider::onSignalChanged()
{
emit updated();
}
void QAndroidCellDataProvider::cellUpdate(int cid, int lac, int mcc, int mnc, int rssi)
void QAndroidCellDataProvider::requestData()
{
CellDataPtr new_data(new CellData);
new_data->cellId = cid;
new_data->locationAreaCode = lac;
new_data->mobileCountryCode = mcc;
new_data->mobileNetworkCode = mnc;
new_data->signalStrength = rssi;
new_data->timingAdvance = 0;
if (last_data_)
current_data_ = CellDataPtr::create();
try
{
if (*new_data==*last_data_)
if (isJniReady())
{
return;
jni()->callVoid("requestData");
{
QWriteLocker locker(&lock_data_);
last_data_ = current_data_;
}
emit dataReady();
}
}
catch (const std::exception & ex)
{
qCritical() << "JNI exception in QAndroidCellDataProvider:" << ex.what();
}
}
last_data_ = new_data;
emit update();
void QAndroidCellDataProvider::cellUpdate(jstring type, jint cid, jint lac, jint mcc, jint mnc, jint rssi, jint ta)
{
if (current_data_ && cid > 0 && cid != CellData::java_integer_max_value)
{
current_data_->data_.push_back(CellData::Data(cid));
if (isJniReady())
{
QJniEnvPtr jep;
current_data_->data_.back().radio_type_ = jep.JStringToQString(type);
}
current_data_->data_.back().location_area_code_ = lac;
current_data_->data_.back().mobile_country_code_ = mcc;
current_data_->data_.back().mobile_network_code_ = mnc;
current_data_->data_.back().signal_strength_ = rssi;
current_data_->data_.back().timing_advance_ = ta;
}
}
CellDataPtr QAndroidCellDataProvider::getLastData()
{
QReadLocker locker(&lock_data_);
return last_data_;
}
}
} // namespace Mobility
......@@ -39,6 +39,7 @@
#pragma once
#include <QtCore/QObject>
#include <QtCore/QReadWriteLock>
#include <QJniHelpers.h>
#include "IJniObjectLinker.h"
#include "CellData.h"
......@@ -59,18 +60,24 @@ public:
CellDataPtr getLastData();
private:
friend void JNICALL Java_CellListener_cellUpdate(JNIEnv *, jobject, jlong native_ptr, jint cid, jint lac, jint mcc, jint mnc, jint rssi);
void cellUpdate(int cid, int lac, int mcc, int mnc, int rssi);
friend void JNICALL Java_CellListener_cellUpdate(JNIEnv *, jobject, jlong native_ptr, jstring type, jint cid, jint lac, jint mcc, jint mnc, jint rssi, jint ta);
friend void JNICALL JNICALL Java_CellListener_onSignalChanged(JNIEnv *, jobject, jlong native_ptr);
void cellUpdate(jstring type, jint cid, jint lac, jint mcc, jint mnc, jint rssi, jint ta);
void onSignalChanged();
public slots:
void start();
void stop();
void requestData();
signals:
void update();
void updated();
void dataReady();
private:
CellDataPtr last_data_;
CellDataPtr current_data_;
QReadWriteLock lock_data_;
};
}
......@@ -53,7 +53,6 @@ Q_DECL_EXPORT void JNICALL Java_WifiListener_scanUpdate(JNIEnv *, jobject, jlong
static const JNINativeMethod methods[] = {
{"getContext", "()Landroid/content/Context;", reinterpret_cast<void*>(QAndroidQPAPluginGap::getCurrentContextNoThrow)},
// private native void scanUpdate(long native_ptr);
{"scanUpdate", "(J)V", reinterpret_cast<void*>(Java_WifiListener_scanUpdate)},
};
......
/*
Offscreen Android Views library for Qt
Authors:
Vyacheslav O. Koscheev <vok1980@gmail.com>
Ivan Avdeev marflon@gmail.com
Sergey A. Galin sergey.galin@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.
*/
package ru.dublgis.androidhelpers.mobility;
public class CellData
{
public int cellId_;
public int locationAreaCode_;
public int mobileCountryCode_;
public int mobileNetworkCode_;
public int signalStrength_;
public int timingAdvance_;
public CellData(int cellId, int locationAreaCode,
int mobileCountryCode, int mobileNetworkCode,
int signalStrength, int timingAdvance )
{
cellId_ = cellId;
locationAreaCode_ = locationAreaCode;
mobileCountryCode_ = mobileCountryCode;
mobileNetworkCode_ = mobileNetworkCode;
signalStrength_ = signalStrength;
timingAdvance_ = timingAdvance;
}
@Override
public String toString()
{
return "[cid:"+cellId_+", lac:"+locationAreaCode_+
", mcc:"+mobileCountryCode_+", mnc:"+mobileNetworkCode_+
", rssi:"+signalStrength_+", ta:"+timingAdvance_+"]";
}
}
......@@ -39,255 +39,303 @@
package ru.dublgis.androidhelpers.mobility;
import android.content.Context;
import android.os.Build;
import android.os.Looper;
import android.telephony.TelephonyManager;
import android.telephony.PhoneStateListener;
import android.support.v4.app.ActivityCompat;
import android.telephony.CellInfo;
import android.telephony.CellInfoCdma;
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoLte;
import android.telephony.CellInfoWcdma;
import android.telephony.CellLocation;
import android.telephony.gsm.GsmCellLocation;
import android.telephony.cdma.CdmaCellLocation;
import android.telephony.NeighboringCellInfo;
import android.telephony.PhoneStateListener;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaCellLocation;
import android.telephony.gsm.GsmCellLocation;
import java.util.List;
import ru.dublgis.androidhelpers.Log;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
public class CellListener
{
final static String LOG_TAG = "Grym/CellListener";
final static boolean verbose = false;
private TelephonyManager mTelMan = null;
private CellLocation mLastLocation = null;
public class CellListener {
private final static String TAG = "Grym/CellListener";
private volatile long native_ptr_ = 0;
private final static boolean verbose = false;
private TrueCellListener mListener = null;
private TelephonyManager mManager = null;
private SignalStrength mSignalStrengthLast = null;
private CellListenerImpl mListener = null;
private Thread mListenerThread = null;
private Looper mListenerLooper = null;
public CellListener(long native_ptr)
{
public CellListener(long native_ptr) {
native_ptr_ = native_ptr;
}
//! Called from C++ to notify us that the associated C++ object is being destroyed.
public void cppDestroyed()
{
public void cppDestroyed() {
native_ptr_ = 0;
}
private class TrueCellListener extends PhoneStateListener
{
public synchronized void onCellLocationChanged(CellLocation location)
{
if (verbose)
{
Log.d(LOG_TAG,"CellListener onCellLocationChanged: "+location);
public synchronized boolean start() {
Log.d(TAG, "start");
try {
if (mManager == null) {
mManager = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
}
mLastLocation = location;
Runnable listenRunnable = new Runnable() {
@Override
public void run() {
if (mManager != null) {
try {
Looper.prepare();
mListenerLooper = Looper.myLooper();
mListener = new CellListenerImpl();
mManager.listen(mListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
Looper.loop();
mManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
}
public synchronized void onSignalStrengthsChanged(SignalStrength signalStrength)
{
if (verbose)
{
Log.d(LOG_TAG,"CellListener onSignalStrengthsChanged: "+signalStrength);
catch (Throwable ex) {
Log.e(TAG, "Failed to start TelephonyManager listener", ex);
}
try
{
CellData data = null;
if (signalStrength.isGsm())
{
// Calculate dBm from asu
int dbm = signalStrength.getGsmSignalStrength();
if (dbm == 99)
{
dbm = -113;
}
else if ((dbm >= 0) && (dbm <= 31))
{
dbm = dbm * 2 - 113;
}
else
{
dbm = -51;
}
};
GsmCellLocation loc = (GsmCellLocation)mLastLocation;
if (loc != null)
{
data = new CellData(loc.getCid(), loc.getLac(), -1, -1, dbm, 0);
}
} else {
CdmaCellLocation loc = (CdmaCellLocation)mLastLocation;
if (loc != null)
{
data = new CellData(loc.getBaseStationId(), loc.getNetworkId(),
-1, loc.getSystemId(), signalStrength.getCdmaDbm(), 0);
mListenerThread = new Thread(listenRunnable, "Listen TelephonyManager");
mListenerThread.start();
return true;
} catch (Throwable e) {
Log.e(TAG, "Exception while starting cell listener: ", e);
return false;
}
}
if (data == null)
{
if (verbose)
{
Log.d(LOG_TAG,"CellListener onCellLocationChanged: null data!");
}
return;
}
String op = mTelMan.getNetworkOperator();
public synchronized void stop() {
Log.d(TAG, "stop");
try
{
if (op != null && op.length() > 0)
{
if (data.mobileCountryCode_ == -1)
{
try
{
data.mobileCountryCode_ = Integer.parseInt(op.substring(0, 3));
}
catch(Exception e)
{
Log.e(LOG_TAG, "Failed to parse mobile country code from: \""+op+"\"");
}
}
if (data.mobileNetworkCode_ == -1)
{
try
{
data.mobileNetworkCode_ = Integer.parseInt(op.substring(3));
}
catch(Exception e)
{
Log.e(LOG_TAG, "Failed to parse mobile network code from: \""+op+"\"");
}
}
}
else // (op is empty)
{
// (Normal situaution without SIM card.)
// Log.d(LOG_TAG, "Network operator string is empty.");
try {
if (mManager != null) {
mManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
}
}
catch(Exception e)
{
Log.e(LOG_TAG, "Could not get network and country codes. CDMA?", e);
catch (SecurityException ex) {
Log.e(TAG, "Failed to stop listener", ex);
}
cellUpdate(native_ptr_, data.cellId_, data.locationAreaCode_, data.mobileCountryCode_, data.mobileNetworkCode_, data.signalStrength_);
}
catch(Exception e)
{
Log.e(LOG_TAG, "Exception in onSignalStrengthsChanged: "+e);
if (null != mListenerLooper) {
mListenerLooper.quit();
}
try {
if (mListenerThread.isAlive()) {
mListenerThread.join(300);
}
} catch (InterruptedException e) {
Log.e(TAG, "Exception in cppDestroyed: ", e);
}
}
private void reportDataCellInfo(List<CellInfo> cellInfoList) {
for (CellInfo cellInfo : cellInfoList) {
if (Build.VERSION.SDK_INT >= 17) {
if (cellInfo instanceof CellInfoCdma) {
CellInfoCdma cellInfoCdma = (CellInfoCdma) cellInfo;
cellUpdate(
native_ptr_,
"cdma",
cellInfoCdma.getCellIdentity().getBasestationId(), // Base Station Id 0..65535, Integer.MAX_VALUE if unknown
cellInfoCdma.getCellIdentity().getNetworkId(), // Network Id 0..65535, Integer.MAX_VALUE if unknown
Integer.MAX_VALUE,
cellInfoCdma.getCellIdentity().getSystemId(), // System Id 0..32767, Integer.MAX_VALUE if unknown
cellInfoCdma.getCellSignalStrength().getCdmaDbm(), // Get the CDMA RSSI value in dBm
Integer.MAX_VALUE
);
}
if (cellInfo instanceof CellInfoGsm) {
CellInfoGsm cellInfoGsm = (CellInfoGsm) cellInfo;
cellUpdate(
native_ptr_,
"gsm",
cellInfoGsm.getCellIdentity().getCid(), // CID Either 16-bit GSM Cell Identity described in TS 27.007, 0..65535, Integer.MAX_VALUE if unknown
cellInfoGsm.getCellIdentity().getLac(), // 16-bit Location Area Code, 0..65535, Integer.MAX_VALUE if unknown
cellInfoGsm.getCellIdentity().getMcc(), // 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
cellInfoGsm.getCellIdentity().getMnc(), // 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
cellInfoGsm.getCellSignalStrength().getDbm(), // Get the signal strength as dBm
android.os.Build.VERSION.SDK_INT >= 26 ? cellInfoGsm.getCellSignalStrength().getTimingAdvance() : Integer.MAX_VALUE // Get the GSM timing advance between 0..219 symbols (normally 0..63). Integer.MAX_VALUE is reported when there is no RR connection.
);
}
if (cellInfo instanceof CellInfoLte) {
CellInfoLte cellInfoLte = (CellInfoLte) cellInfo;
cellUpdate(
native_ptr_,
"lte",
cellInfoLte.getCellIdentity().getCi(), // 28-bit Cell Identity, Integer.MAX_VALUE if unknown
cellInfoLte.getCellIdentity().getTac(), // 16-bit Tracking Area Code, Integer.MAX_VALUE if unknown
cellInfoLte.getCellIdentity().getMcc(), // 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
cellInfoLte.getCellIdentity().getMnc(), // 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
cellInfoLte.getCellSignalStrength().getDbm(), // Get signal strength as dBm
cellInfoLte.getCellSignalStrength().getTimingAdvance() // Get the timing advance value for LTE, as a value between 0..63. Integer.MAX_VALUE is reported when there is no active RRC connection. Refer to 3GPP 36.213 Sec 4.2.3
);
}
}
if (Build.VERSION.SDK_INT >= 18) {
if (cellInfo instanceof CellInfoWcdma) {
CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) cellInfo;
cellUpdate(
native_ptr_,
"wcdma",
cellInfoWcdma.getCellIdentity().getCid(), // CID 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, Integer.MAX_VALUE if unknown
cellInfoWcdma.getCellIdentity().getLac(), // 16-bit Location Area Code, 0..65535, Integer.MAX_VALUE if unknown
cellInfoWcdma.getCellIdentity().getMcc(), // 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
cellInfoWcdma.getCellIdentity().getMnc(), // 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
cellInfoWcdma.getCellSignalStrength().getDbm(), // Get the signal strength as dBm
Integer.MAX_VALUE
);
}
}
}
}