Add API
This commit is contained in:
parent
18b16dcc1f
commit
4b5b3251ae
60
api/build.gradle
Normal file
60
api/build.gradle
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion androidCompileSdk
|
||||||
|
buildToolsVersion "$androidBuildVersionTools"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
versionName version
|
||||||
|
minSdkVersion androidMinSdk
|
||||||
|
targetSdkVersion androidTargetSdk
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
targetCompatibility = 1.8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
release(MavenPublication) {
|
||||||
|
pom {
|
||||||
|
name = 'UnifiedNlp API'
|
||||||
|
description = 'API interfaces and helpers to create backends for UnifiedNlp'
|
||||||
|
url = 'https://github.com/microg/UnifiedNlp'
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name = 'The Apache Software License, Version 2.0'
|
||||||
|
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id = 'microg'
|
||||||
|
name = 'microG Team'
|
||||||
|
}
|
||||||
|
developer {
|
||||||
|
id = 'mar-v-in'
|
||||||
|
name = 'Marvin W.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
url = 'https://github.com/microg/UnifiedNlp'
|
||||||
|
connection = 'scm:git:https://github.com/microg/UnifiedNlp.git'
|
||||||
|
developerConnection = 'scm:git:ssh://github.com/microg/UnifiedNlp.git'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
from components.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
api/src/main/AndroidManifest.xml
Normal file
19
api/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
~ SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest package="org.microg.nlp.api"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<meta-data
|
||||||
|
android:name="org.microg.nlp.API_VERSION"
|
||||||
|
android:value="@string/nlp_api_version"/>
|
||||||
|
<activity
|
||||||
|
android:name=".MPermissionHelperActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
27
api/src/main/aidl/org/microg/nlp/api/GeocoderBackend.aidl
Normal file
27
api/src/main/aidl/org/microg/nlp/api/GeocoderBackend.aidl
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.Address;
|
||||||
|
|
||||||
|
interface GeocoderBackend {
|
||||||
|
void open();
|
||||||
|
List<Address> getFromLocation(double latitude, double longitude, int maxResults, String locale);
|
||||||
|
List<Address> getFromLocationName(String locationName, int maxResults, double lowerLeftLatitude,
|
||||||
|
double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude,
|
||||||
|
String locale);
|
||||||
|
void close();
|
||||||
|
Intent getInitIntent();
|
||||||
|
Intent getSettingsIntent();
|
||||||
|
Intent getAboutIntent();
|
||||||
|
List<Address> getFromLocationWithOptions(double latitude, double longitude, int maxResults,
|
||||||
|
String locale, in Bundle options);
|
||||||
|
List<Address> getFromLocationNameWithOptions(String locationName, int maxResults,
|
||||||
|
double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude,
|
||||||
|
double upperRightLongitude, String locale, in Bundle options);
|
||||||
|
}
|
20
api/src/main/aidl/org/microg/nlp/api/LocationBackend.aidl
Normal file
20
api/src/main/aidl/org/microg/nlp/api/LocationBackend.aidl
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import org.microg.nlp.api.LocationCallback;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
interface LocationBackend {
|
||||||
|
void open(LocationCallback callback);
|
||||||
|
Location update();
|
||||||
|
void close();
|
||||||
|
Intent getInitIntent();
|
||||||
|
Intent getSettingsIntent();
|
||||||
|
Intent getAboutIntent();
|
||||||
|
Location updateWithOptions(in Bundle options);
|
||||||
|
}
|
12
api/src/main/aidl/org/microg/nlp/api/LocationCallback.aidl
Normal file
12
api/src/main/aidl/org/microg/nlp/api/LocationCallback.aidl
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
interface LocationCallback {
|
||||||
|
void report(in Location location);
|
||||||
|
}
|
7
api/src/main/java/android/location/Address.aidl
Normal file
7
api/src/main/java/android/location/Address.aidl
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2008, The Android Open Source Project
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.location;
|
||||||
|
parcelable Address;
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public class AbstractBackendHelper {
|
||||||
|
public static final String TAG = "BackendHelper";
|
||||||
|
protected final Context context;
|
||||||
|
protected State state = State.DISABLED;
|
||||||
|
protected boolean currentDataUsed = true;
|
||||||
|
|
||||||
|
public AbstractBackendHelper(Context context) {
|
||||||
|
if (context == null)
|
||||||
|
throw new IllegalArgumentException("context must not be null");
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this in {@link org.microg.nlp.api.LocationBackendService#onOpen()}.
|
||||||
|
*/
|
||||||
|
public synchronized void onOpen() {
|
||||||
|
if (state == State.WAITING || state == State.SCANNING) {
|
||||||
|
Log.w(TAG, "Do not call onOpen if not closed before");
|
||||||
|
}
|
||||||
|
currentDataUsed = true;
|
||||||
|
state = State.WAITING;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this in {@link org.microg.nlp.api.LocationBackendService#onClose()}.
|
||||||
|
*/
|
||||||
|
public synchronized void onClose() {
|
||||||
|
if (state == State.DISABLED || state == State.DISABLING) {
|
||||||
|
Log.w(TAG, "Do not call onClose if not opened before");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state == State.WAITING) {
|
||||||
|
state = State.DISABLED;
|
||||||
|
} else {
|
||||||
|
state = State.DISABLING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this in {@link org.microg.nlp.api.LocationBackendService#update()}.
|
||||||
|
*/
|
||||||
|
public synchronized void onUpdate() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getRequiredPermissions() {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum State {DISABLED, WAITING, SCANNING, DISABLING}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
|
public abstract class AbstractBackendService extends Service {
|
||||||
|
|
||||||
|
public static final String TAG = "BackendService";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return getBackend();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after a connection was setup
|
||||||
|
*/
|
||||||
|
protected void onOpen() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before connection closure
|
||||||
|
*/
|
||||||
|
protected void onClose() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Intent getInitIntent() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameReturnValue")
|
||||||
|
protected Intent getSettingsIntent() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameReturnValue")
|
||||||
|
protected Intent getAboutIntent() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onUnbind(Intent intent) {
|
||||||
|
try {
|
||||||
|
disconnect();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
return super.onUnbind(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void disconnect();
|
||||||
|
|
||||||
|
protected abstract IBinder getBackend();
|
||||||
|
|
||||||
|
protected String getServiceApiVersion() {
|
||||||
|
return Utils.getServiceApiVersion(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getSelfApiVersion() {
|
||||||
|
return Utils.getSelfApiVersion(this);
|
||||||
|
}
|
||||||
|
}
|
155
api/src/main/java/org/microg/nlp/api/BluetoothBackendHelper.java
Normal file
155
api/src/main/java/org/microg/nlp/api/BluetoothBackendHelper.java
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||||
|
import static android.Manifest.permission.BLUETOOTH;
|
||||||
|
import static android.Manifest.permission.BLUETOOTH_ADMIN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to support backend using Device for geolocation.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"MissingPermission", "WeakerAccess", "unused"})
|
||||||
|
public class BluetoothBackendHelper extends AbstractBackendHelper {
|
||||||
|
private final static IntentFilter bluetoothBroadcastFilter =
|
||||||
|
new IntentFilter();
|
||||||
|
|
||||||
|
private final Listener listener;
|
||||||
|
private final BluetoothAdapter bluetoothAdapter;
|
||||||
|
private final Set<Device> devices = new HashSet<>();
|
||||||
|
private final BroadcastReceiver bluetoothBroadcastReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
|
||||||
|
devices.clear();
|
||||||
|
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
|
||||||
|
onBluetoothChanged();
|
||||||
|
} else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
|
||||||
|
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||||
|
if (device != null) {
|
||||||
|
int rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
|
||||||
|
Device deviceDiscovered = new Device(device.getAddress(), device.getName(), rssi);
|
||||||
|
devices.add(deviceDiscovered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public BluetoothBackendHelper(Context context, Listener listener){
|
||||||
|
super(context);
|
||||||
|
if (listener == null)
|
||||||
|
throw new IllegalArgumentException("listener must not be null");
|
||||||
|
this.listener = listener;
|
||||||
|
this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
bluetoothBroadcastFilter.addAction(BluetoothDevice.ACTION_FOUND);
|
||||||
|
bluetoothBroadcastFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
|
||||||
|
bluetoothBroadcastFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void onOpen() {
|
||||||
|
super.onOpen();
|
||||||
|
bluetoothBroadcastFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
|
||||||
|
bluetoothBroadcastFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
|
||||||
|
bluetoothBroadcastFilter.addAction(BluetoothDevice.ACTION_FOUND);
|
||||||
|
context.registerReceiver(bluetoothBroadcastReceiver, bluetoothBroadcastFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void onClose() {
|
||||||
|
super.onClose();
|
||||||
|
context.unregisterReceiver(bluetoothBroadcastReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void onUpdate() {
|
||||||
|
if (!currentDataUsed) {
|
||||||
|
listener.onDevicesChanged(getDevices());
|
||||||
|
} else {
|
||||||
|
scanBluetooth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getRequiredPermissions() {
|
||||||
|
return new String[]{BLUETOOTH, BLUETOOTH_ADMIN, ACCESS_COARSE_LOCATION};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onBluetoothChanged() {
|
||||||
|
if (loadBluetooths()) {
|
||||||
|
listener.onDevicesChanged(getDevices());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized boolean scanBluetooth() {
|
||||||
|
if (state == State.DISABLED)
|
||||||
|
return false;
|
||||||
|
if (bluetoothAdapter.isEnabled()) {
|
||||||
|
state = State.SCANNING;
|
||||||
|
bluetoothAdapter.startDiscovery();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized boolean loadBluetooths() {
|
||||||
|
currentDataUsed = false;
|
||||||
|
if (state == State.DISABLING)
|
||||||
|
state = State.DISABLED;
|
||||||
|
switch (state) {
|
||||||
|
default:
|
||||||
|
case DISABLED:
|
||||||
|
return false;
|
||||||
|
case SCANNING:
|
||||||
|
state = State.WAITING;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Set<Device> getDevices() {
|
||||||
|
currentDataUsed = true;
|
||||||
|
return new HashSet<>(devices);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Listener {
|
||||||
|
void onDevicesChanged(Set<Device> device);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Device {
|
||||||
|
private final String bssid;
|
||||||
|
private final String name;
|
||||||
|
private final int rssi;
|
||||||
|
|
||||||
|
public String getBssid() { return bssid; }
|
||||||
|
|
||||||
|
public String getName() {return name; }
|
||||||
|
|
||||||
|
public int getRssi() { return rssi; }
|
||||||
|
|
||||||
|
public Device(String bssid, String name, int rssi) {
|
||||||
|
this.bssid = Utils.wellFormedMac(bssid);
|
||||||
|
this.name = name;
|
||||||
|
this.rssi = rssi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Device{" +
|
||||||
|
"name=" + name +
|
||||||
|
", bssid=" + bssid +
|
||||||
|
", rssi=" + rssi +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
558
api/src/main/java/org/microg/nlp/api/CellBackendHelper.java
Normal file
558
api/src/main/java/org/microg/nlp/api/CellBackendHelper.java
Normal file
|
@ -0,0 +1,558 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.telephony.CellIdentity;
|
||||||
|
import android.telephony.CellIdentityCdma;
|
||||||
|
import android.telephony.CellIdentityGsm;
|
||||||
|
import android.telephony.CellIdentityLte;
|
||||||
|
import android.telephony.CellIdentityWcdma;
|
||||||
|
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.CellSignalStrengthCdma;
|
||||||
|
import android.telephony.CellSignalStrengthGsm;
|
||||||
|
import android.telephony.CellSignalStrengthLte;
|
||||||
|
import android.telephony.CellSignalStrengthWcdma;
|
||||||
|
import android.telephony.PhoneStateListener;
|
||||||
|
import android.telephony.SignalStrength;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
|
import android.telephony.cdma.CdmaCellLocation;
|
||||||
|
import android.telephony.gsm.GsmCellLocation;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||||
|
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||||
|
import static android.Manifest.permission.READ_PHONE_STATE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to support backends that use Cells for geolocation.
|
||||||
|
* <p/>
|
||||||
|
* Due to changes in APIs for cell retrieval, this class will only work on Android 4.2+
|
||||||
|
* Support for earlier Android versions might be added later...
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"JavaReflectionMemberAccess", "unused", "WeakerAccess", "deprecation"})
|
||||||
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
public class CellBackendHelper extends AbstractBackendHelper {
|
||||||
|
private final Listener listener;
|
||||||
|
private final TelephonyManager telephonyManager;
|
||||||
|
private final Set<Cell> cells = new HashSet<>();
|
||||||
|
private PhoneStateListener phoneStateListener;
|
||||||
|
private boolean supportsCellInfoChanged = true;
|
||||||
|
|
||||||
|
public static final int MIN_UPDATE_INTERVAL = 30 * 1000;
|
||||||
|
public static final int FALLBACK_UPDATE_INTERVAL = 5 * 60 * 1000;
|
||||||
|
private long lastScan = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of {@link CellBackendHelper}. Call this in
|
||||||
|
* {@link LocationBackendService#onCreate()}.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if either context or listener is null.
|
||||||
|
* @throws IllegalStateException if android version is below 4.2
|
||||||
|
*/
|
||||||
|
public CellBackendHelper(Context context, Listener listener) {
|
||||||
|
super(context);
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||||
|
throw new IllegalStateException("Requires Android 4.2+");
|
||||||
|
if (listener == null)
|
||||||
|
throw new IllegalArgumentException("listener must not be null");
|
||||||
|
this.listener = listener;
|
||||||
|
this.telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getMcc() {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(telephonyManager.getNetworkOperator().substring(0, 3));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getMnc() {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(telephonyManager.getNetworkOperator().substring(3));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Cell.CellType getCellType(int networkType) {
|
||||||
|
switch (networkType) {
|
||||||
|
case TelephonyManager.NETWORK_TYPE_GPRS:
|
||||||
|
case TelephonyManager.NETWORK_TYPE_EDGE:
|
||||||
|
return Cell.CellType.GSM;
|
||||||
|
case TelephonyManager.NETWORK_TYPE_UMTS:
|
||||||
|
case TelephonyManager.NETWORK_TYPE_HSDPA:
|
||||||
|
case TelephonyManager.NETWORK_TYPE_HSUPA:
|
||||||
|
case TelephonyManager.NETWORK_TYPE_HSPA:
|
||||||
|
case TelephonyManager.NETWORK_TYPE_HSPAP:
|
||||||
|
return Cell.CellType.UMTS;
|
||||||
|
case TelephonyManager.NETWORK_TYPE_LTE:
|
||||||
|
return Cell.CellType.LTE;
|
||||||
|
case TelephonyManager.NETWORK_TYPE_EVDO_0:
|
||||||
|
case TelephonyManager.NETWORK_TYPE_EVDO_A:
|
||||||
|
case TelephonyManager.NETWORK_TYPE_EVDO_B:
|
||||||
|
case TelephonyManager.NETWORK_TYPE_1xRTT:
|
||||||
|
case TelephonyManager.NETWORK_TYPE_EHRPD:
|
||||||
|
case TelephonyManager.NETWORK_TYPE_IDEN:
|
||||||
|
return Cell.CellType.CDMA;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ChainOfInstanceofChecks")
|
||||||
|
private Cell parseCellInfo(CellInfo info) {
|
||||||
|
try {
|
||||||
|
if (info instanceof CellInfoGsm) {
|
||||||
|
CellIdentityGsm identity = ((CellInfoGsm) info).getCellIdentity();
|
||||||
|
if (identity.getMcc() == Integer.MAX_VALUE) return null;
|
||||||
|
CellSignalStrengthGsm strength = ((CellInfoGsm) info).getCellSignalStrength();
|
||||||
|
return new Cell(Cell.CellType.GSM, identity.getMcc(), identity.getMnc(),
|
||||||
|
identity.getLac(), identity.getCid(), -1, strength.getDbm());
|
||||||
|
} else if (info instanceof CellInfoCdma) {
|
||||||
|
CellIdentityCdma identity = ((CellInfoCdma) info).getCellIdentity();
|
||||||
|
CellSignalStrengthCdma strength = ((CellInfoCdma) info).getCellSignalStrength();
|
||||||
|
return new Cell(Cell.CellType.CDMA, getMcc(), identity.getSystemId(),
|
||||||
|
identity.getNetworkId(), identity.getBasestationId(), -1, strength.getDbm());
|
||||||
|
} else {
|
||||||
|
return parceCellInfo18(info);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"ChainOfInstanceofChecks", "deprecation"})
|
||||||
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||||
|
private Cell parceCellInfo18(CellInfo info) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return null;
|
||||||
|
if (info instanceof CellInfoWcdma) {
|
||||||
|
CellIdentityWcdma identity = ((CellInfoWcdma) info).getCellIdentity();
|
||||||
|
if (identity.getMcc() == Integer.MAX_VALUE) return null;
|
||||||
|
CellSignalStrengthWcdma strength = ((CellInfoWcdma) info).getCellSignalStrength();
|
||||||
|
return new Cell(Cell.CellType.UMTS, identity.getMcc(), identity.getMnc(),
|
||||||
|
identity.getLac(), identity.getCid(), identity.getPsc(), strength.getDbm());
|
||||||
|
} else if (info instanceof CellInfoLte) {
|
||||||
|
CellIdentityLte identity = ((CellInfoLte) info).getCellIdentity();
|
||||||
|
if (identity.getMcc() == Integer.MAX_VALUE) return null;
|
||||||
|
CellSignalStrengthLte strength = ((CellInfoLte) info).getCellSignalStrength();
|
||||||
|
return new Cell(Cell.CellType.LTE, identity.getMcc(), identity.getMnc(),
|
||||||
|
identity.getTac(), identity.getCi(), identity.getPci(), strength.getDbm());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private Cell parseCellInfo(android.telephony.NeighboringCellInfo info) {
|
||||||
|
try {
|
||||||
|
if (getCellType(info.getNetworkType()) != Cell.CellType.GSM) return null;
|
||||||
|
return new Cell(Cell.CellType.GSM, getMcc(), getMnc(), info.getLac(), info.getCid(),
|
||||||
|
info.getPsc(), info.getRssi());
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCellsChanged(List<CellInfo> cellInfo) {
|
||||||
|
lastScan = System.currentTimeMillis();
|
||||||
|
if (loadCells(cellInfo)) {
|
||||||
|
listener.onCellsChanged(getCells());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will fix empty MNC since Android 9 with 0-prefixed MNCs.
|
||||||
|
* Issue: https://issuetracker.google.com/issues/113560852
|
||||||
|
*/
|
||||||
|
private void fixEmptyMnc(List<CellInfo> cellInfo) {
|
||||||
|
if (Build.VERSION.SDK_INT < 28 || cellInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String networkOperator = telephonyManager.getNetworkOperator();
|
||||||
|
|
||||||
|
if (networkOperator.length() < 5 || networkOperator.charAt(3) != '0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String mnc = networkOperator.substring(3);
|
||||||
|
|
||||||
|
for (CellInfo info : cellInfo) {
|
||||||
|
if (!info.isRegistered()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object identity = null;
|
||||||
|
|
||||||
|
if (info instanceof CellInfoGsm) {
|
||||||
|
identity = ((CellInfoGsm) info).getCellIdentity();
|
||||||
|
} else if (info instanceof CellInfoWcdma) {
|
||||||
|
identity = ((CellInfoWcdma) info).getCellIdentity();
|
||||||
|
} else if (info instanceof CellInfoLte) {
|
||||||
|
identity = ((CellInfoLte) info).getCellIdentity();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (identity == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Field mncField = CellIdentity.class.getDeclaredField("mMncStr");
|
||||||
|
mncField.setAccessible(true);
|
||||||
|
if (mncField.get(identity) == null) {
|
||||||
|
mncField.set(identity, mnc);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will fix values returned by {@link TelephonyManager#getAllCellInfo()} as described
|
||||||
|
* here: https://github.com/mozilla/ichnaea/issues/340
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"ChainOfInstanceofChecks", "MagicNumber", "ConstantConditions"})
|
||||||
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||||
|
private void fixShortMncBug(List<CellInfo> cellInfo) {
|
||||||
|
if (cellInfo == null) return;
|
||||||
|
String networkOperator = telephonyManager.getNetworkOperator();
|
||||||
|
if (networkOperator.length() != 5) return;
|
||||||
|
int realMnc = Integer.parseInt(networkOperator.substring(3));
|
||||||
|
boolean theBug = false;
|
||||||
|
for (CellInfo info : cellInfo) {
|
||||||
|
if (info instanceof CellInfoCdma) return;
|
||||||
|
if (info.isRegistered()) {
|
||||||
|
Cell cell = parseCellInfo(info);
|
||||||
|
if (cell == null) continue;
|
||||||
|
int infoMnc = cell.getMnc();
|
||||||
|
if (infoMnc == (realMnc * 10 + 15)) {
|
||||||
|
theBug = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (theBug) {
|
||||||
|
for (CellInfo info : cellInfo) {
|
||||||
|
Object identity = null;
|
||||||
|
if (info instanceof CellInfoGsm)
|
||||||
|
identity = ((CellInfoGsm) info).getCellIdentity();
|
||||||
|
else if (info instanceof CellInfoLte)
|
||||||
|
identity = ((CellInfoLte) info).getCellIdentity();
|
||||||
|
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 &&
|
||||||
|
info instanceof CellInfoWcdma)
|
||||||
|
identity = ((CellInfoWcdma) info).getCellIdentity();
|
||||||
|
if (identity == null) continue;
|
||||||
|
try {
|
||||||
|
Field mncField = identity.getClass().getDeclaredField("mMnc");
|
||||||
|
mncField.setAccessible(true);
|
||||||
|
int mnc = (Integer) mncField.get(identity);
|
||||||
|
if (mnc >= 25 && mnc <= 1005) {
|
||||||
|
mnc = (mnc - 15) / 10;
|
||||||
|
mncField.setInt(identity, mnc);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasCid(long cid) {
|
||||||
|
for (Cell cell : cells) {
|
||||||
|
if (cell.getCid() == cid) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is to support some broken implementations that do not support {@link TelephonyManager#getAllCellInfo()}
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("ChainOfInstanceofChecks")
|
||||||
|
private CellInfo fromCellLocation(CellLocation cellLocation) {
|
||||||
|
try {
|
||||||
|
if (cellLocation instanceof GsmCellLocation) {
|
||||||
|
GsmCellLocation gsmCellLocation = (GsmCellLocation) cellLocation;
|
||||||
|
CellIdentityGsm identity = CellIdentityGsm.class.getConstructor(int.class, int.class, int.class, int.class)
|
||||||
|
.newInstance(getMcc(), getMnc(), gsmCellLocation.getLac(), gsmCellLocation.getCid());
|
||||||
|
CellSignalStrengthGsm strength = CellSignalStrengthGsm.class.newInstance();
|
||||||
|
CellInfoGsm info = CellInfoGsm.class.newInstance();
|
||||||
|
CellInfoGsm.class.getMethod("setCellIdentity", CellIdentityGsm.class).invoke(info, identity);
|
||||||
|
CellInfoGsm.class.getMethod("setCellSignalStrength", CellSignalStrengthGsm.class).invoke(info, strength);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
if (cellLocation instanceof CdmaCellLocation) {
|
||||||
|
CdmaCellLocation cdmaCellLocation = (CdmaCellLocation) cellLocation;
|
||||||
|
CellIdentityCdma identity = CellIdentityCdma.class.getConstructor(int.class, int.class, int.class, int.class, int.class)
|
||||||
|
.newInstance(cdmaCellLocation.getNetworkId(), cdmaCellLocation.getSystemId(), cdmaCellLocation.getBaseStationId(),
|
||||||
|
cdmaCellLocation.getBaseStationLongitude(), cdmaCellLocation.getBaseStationLatitude());
|
||||||
|
CellSignalStrengthCdma strength = CellSignalStrengthCdma.class.newInstance();
|
||||||
|
CellInfoCdma info = CellInfoCdma.class.newInstance();
|
||||||
|
CellInfoCdma.class.getMethod("setCellIdentity", CellIdentityCdma.class).invoke(info, identity);
|
||||||
|
CellInfoCdma.class.getMethod("setCellSignalStrength", CellSignalStrengthCdma.class).invoke(info, strength);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@SuppressLint({"DiscouragedPrivateApi", "deprecation"})
|
||||||
|
private synchronized boolean loadCells(List<CellInfo> cellInfo) {
|
||||||
|
int oldHash = cells.hashCode();
|
||||||
|
cells.clear();
|
||||||
|
currentDataUsed = false;
|
||||||
|
try {
|
||||||
|
if (cellInfo != null) {
|
||||||
|
fixEmptyMnc(cellInfo);
|
||||||
|
fixShortMncBug(cellInfo);
|
||||||
|
for (CellInfo info : cellInfo) {
|
||||||
|
Cell cell = parseCellInfo(info);
|
||||||
|
if (cell == null) continue;
|
||||||
|
cells.add(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Method getNeighboringCellInfo = TelephonyManager.class.getDeclaredMethod("getNeighboringCellInfo");
|
||||||
|
List<android.telephony.NeighboringCellInfo> neighboringCellInfo = (List<android.telephony.NeighboringCellInfo>) getNeighboringCellInfo.invoke(telephonyManager);
|
||||||
|
if (neighboringCellInfo != null) {
|
||||||
|
for (android.telephony.NeighboringCellInfo info : neighboringCellInfo) {
|
||||||
|
if (!hasCid(info.getCid())) {
|
||||||
|
Cell cell = parseCellInfo(info);
|
||||||
|
if (cell == null) continue;
|
||||||
|
cells.add(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
if (state == State.DISABLING)
|
||||||
|
state = State.DISABLED;
|
||||||
|
switch (state) {
|
||||||
|
default:
|
||||||
|
case DISABLED:
|
||||||
|
return false;
|
||||||
|
case SCANNING:
|
||||||
|
state = State.WAITING;
|
||||||
|
return cells.hashCode() != oldHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Set<Cell> getCells() {
|
||||||
|
currentDataUsed = true;
|
||||||
|
return new HashSet<>(cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this in {@link org.microg.nlp.api.LocationBackendService#onOpen()}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void onOpen() {
|
||||||
|
super.onOpen();
|
||||||
|
|
||||||
|
if (phoneStateListener == null) {
|
||||||
|
Handler mainHandler = new Handler(context.getMainLooper());
|
||||||
|
mainHandler.post(() -> {
|
||||||
|
phoneStateListener = new PhoneStateListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCellInfoChanged(List<CellInfo> cellInfo) {
|
||||||
|
if (cellInfo != null && !cellInfo.isEmpty()) {
|
||||||
|
onCellsChanged(cellInfo);
|
||||||
|
} else if (supportsCellInfoChanged) {
|
||||||
|
supportsCellInfoChanged = false;
|
||||||
|
onSignalStrengthsChanged(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
|
||||||
|
if (!supportsCellInfoChanged) {
|
||||||
|
fallbackScan();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
registerPhoneStateListener();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
registerPhoneStateListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void fallbackScan() {
|
||||||
|
if (lastScan + MIN_UPDATE_INTERVAL > System.currentTimeMillis()) return;
|
||||||
|
List<CellInfo> allCellInfo = telephonyManager.getAllCellInfo();
|
||||||
|
if ((allCellInfo == null || allCellInfo.isEmpty()) && telephonyManager.getNetworkType() > 0) {
|
||||||
|
allCellInfo = new ArrayList<>();
|
||||||
|
CellLocation cellLocation = telephonyManager.getCellLocation();
|
||||||
|
CellInfo cellInfo = fromCellLocation(cellLocation);
|
||||||
|
if (cellInfo != null) allCellInfo.add(cellInfo);
|
||||||
|
}
|
||||||
|
onCellsChanged(allCellInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void registerPhoneStateListener() {
|
||||||
|
try {
|
||||||
|
telephonyManager.listen(phoneStateListener,
|
||||||
|
PhoneStateListener.LISTEN_CELL_INFO
|
||||||
|
| PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Can't listen
|
||||||
|
phoneStateListener = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this in {@link org.microg.nlp.api.LocationBackendService#onClose()}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void onClose() {
|
||||||
|
super.onClose();
|
||||||
|
if (phoneStateListener != null)
|
||||||
|
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void onUpdate() {
|
||||||
|
if (!currentDataUsed) {
|
||||||
|
listener.onCellsChanged(getCells());
|
||||||
|
} else {
|
||||||
|
state = State.SCANNING;
|
||||||
|
if (lastScan + FALLBACK_UPDATE_INTERVAL < System.currentTimeMillis()) {
|
||||||
|
fallbackScan();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getRequiredPermissions() {
|
||||||
|
return new String[]{READ_PHONE_STATE, ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION};
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Listener {
|
||||||
|
void onCellsChanged(Set<Cell> cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cell {
|
||||||
|
private CellType type;
|
||||||
|
private int mcc;
|
||||||
|
private int mnc;
|
||||||
|
private int lac;
|
||||||
|
private long cid;
|
||||||
|
private int psc;
|
||||||
|
private int signal;
|
||||||
|
|
||||||
|
public Cell(CellType type, int mcc, int mnc, int lac, long cid, int psc, int signal) {
|
||||||
|
if (type == null)
|
||||||
|
throw new IllegalArgumentException("Each cell has an type!");
|
||||||
|
this.type = type;
|
||||||
|
boolean cdma = type == CellType.CDMA;
|
||||||
|
if (mcc < 0 || mcc > 999)
|
||||||
|
throw new IllegalArgumentException("Invalid MCC: " + mcc);
|
||||||
|
this.mcc = mcc;
|
||||||
|
if (cdma ? (mnc < 1 || mnc > 32767) : (mnc < 0 || mnc > 999))
|
||||||
|
throw new IllegalArgumentException("Invalid MNC: " + mnc);
|
||||||
|
this.mnc = mnc;
|
||||||
|
if (lac < 1 || lac > (cdma ? 65534 : 65533))
|
||||||
|
throw new IllegalArgumentException("Invalid LAC: " + lac);
|
||||||
|
this.lac = lac;
|
||||||
|
if (cid < 0)
|
||||||
|
throw new IllegalArgumentException("Invalid CID: " + cid);
|
||||||
|
this.cid = cid;
|
||||||
|
this.psc = psc;
|
||||||
|
this.signal = signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return RSCP for UMTS, RSRP for LTE, RSSI for GSM and CDMA
|
||||||
|
*/
|
||||||
|
public int getSignal() {
|
||||||
|
return signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMcc() {
|
||||||
|
return mcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMnc() {
|
||||||
|
return mnc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLac() {
|
||||||
|
return lac;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCid() {
|
||||||
|
return cid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPsc() {
|
||||||
|
return psc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Cell cell = (Cell) o;
|
||||||
|
|
||||||
|
if (cid != cell.cid) return false;
|
||||||
|
if (lac != cell.lac) return false;
|
||||||
|
if (mcc != cell.mcc) return false;
|
||||||
|
if (mnc != cell.mnc) return false;
|
||||||
|
if (psc != cell.psc) return false;
|
||||||
|
if (signal != cell.signal) return false;
|
||||||
|
if (type != cell.type) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = type.hashCode();
|
||||||
|
result = 31 * result + mcc;
|
||||||
|
result = 31 * result + mnc;
|
||||||
|
result = 31 * result + lac;
|
||||||
|
result = 31 * result + (int) (cid ^ (cid >>> 32));
|
||||||
|
result = 31 * result + psc;
|
||||||
|
result = 31 * result + signal;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Cell{" +
|
||||||
|
"type=" + type +
|
||||||
|
", mcc=" + mcc +
|
||||||
|
", mnc=" + mnc +
|
||||||
|
", lac=" + lac +
|
||||||
|
", cid=" + cid +
|
||||||
|
(psc != -1 ? (", psc=" + psc) : "") +
|
||||||
|
", signal=" + signal +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CellType {GSM, UMTS, LTE, CDMA}
|
||||||
|
}
|
||||||
|
}
|
24
api/src/main/java/org/microg/nlp/api/Constants.java
Normal file
24
api/src/main/java/org/microg/nlp/api/Constants.java
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
public class Constants {
|
||||||
|
public static final String ACTION_LOCATION_BACKEND = "org.microg.nlp.LOCATION_BACKEND";
|
||||||
|
public static final String ACTION_GEOCODER_BACKEND = "org.microg.nlp.GEOCODER_BACKEND";
|
||||||
|
public static final String ACTION_RELOAD_SETTINGS = "org.microg.nlp.RELOAD_SETTINGS";
|
||||||
|
public static final String ACTION_FORCE_LOCATION = "org.microg.nlp.FORCE_LOCATION";
|
||||||
|
public static final String PERMISSION_FORCE_LOCATION = "org.microg.permission.FORCE_COARSE_LOCATION";
|
||||||
|
public static final String INTENT_EXTRA_LOCATION = "location";
|
||||||
|
public static final String LOCATION_EXTRA_BACKEND_PROVIDER = "SERVICE_BACKEND_PROVIDER";
|
||||||
|
public static final String LOCATION_EXTRA_BACKEND_COMPONENT = "SERVICE_BACKEND_COMPONENT";
|
||||||
|
public static final String LOCATION_EXTRA_OTHER_BACKENDS = "OTHER_BACKEND_RESULTS";
|
||||||
|
public static final String METADATA_BACKEND_SETTINGS_ACTIVITY = "org.microg.nlp.BACKEND_SETTINGS_ACTIVITY";
|
||||||
|
public static final String METADATA_BACKEND_ABOUT_ACTIVITY = "org.microg.nlp.BACKEND_ABOUT_ACTIVITY";
|
||||||
|
public static final String METADATA_BACKEND_INIT_ACTIVITY = "org.microg.nlp.BACKEND_INIT_ACTIVITY";
|
||||||
|
public static final String METADATA_BACKEND_SUMMARY = "org.microg.nlp.BACKEND_SUMMARY";
|
||||||
|
public static final String METADATA_API_VERSION = "org.microg.nlp.API_VERSION";
|
||||||
|
public static final String API_VERSION = "3";
|
||||||
|
}
|
109
api/src/main/java/org/microg/nlp/api/GeocoderBackendService.java
Normal file
109
api/src/main/java/org/microg/nlp/api/GeocoderBackendService.java
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.location.Address;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
|
public abstract class GeocoderBackendService extends AbstractBackendService {
|
||||||
|
|
||||||
|
private final Backend backend = new Backend();
|
||||||
|
private boolean connected = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IBinder getBackend() {
|
||||||
|
return backend;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect() {
|
||||||
|
if (connected) {
|
||||||
|
onClose();
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locale The locale, formatted as a String with underscore (eg. en_US) the resulting
|
||||||
|
* address should be localized in
|
||||||
|
* @see android.location.Geocoder#getFromLocation(double, double, int)
|
||||||
|
*/
|
||||||
|
protected abstract List<Address> getFromLocation(double latitude, double longitude, int maxResults, String locale);
|
||||||
|
|
||||||
|
protected List<Address> getFromLocation(double latitude, double longitude, int maxResults, String locale, Bundle options) {
|
||||||
|
return getFromLocation(latitude, longitude, maxResults, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locale The locale, formatted as a String with underscore (eg. en_US) the resulting
|
||||||
|
* address should be localized in
|
||||||
|
* @see android.location.Geocoder#getFromLocationName(String, int, double, double, double, double)
|
||||||
|
*/
|
||||||
|
protected abstract List<Address> getFromLocationName(String locationName, int maxResults, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, String locale);
|
||||||
|
|
||||||
|
|
||||||
|
protected List<Address> getFromLocationName(String locationName, int maxResults, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, String locale, Bundle options) {
|
||||||
|
return getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Backend extends GeocoderBackend.Stub {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void open() {
|
||||||
|
onOpen();
|
||||||
|
connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Address> getFromLocation(double latitude, double longitude, int maxResults, String locale) {
|
||||||
|
return GeocoderBackendService.this
|
||||||
|
.getFromLocation(latitude, longitude, maxResults, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Address> getFromLocationWithOptions(double latitude, double longitude, int maxResults, String locale, Bundle options) {
|
||||||
|
return GeocoderBackendService.this.getFromLocation(latitude, longitude, maxResults, locale, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Address> getFromLocationName(String locationName, int maxResults, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, String locale) {
|
||||||
|
return GeocoderBackendService.this
|
||||||
|
.getFromLocationName(locationName, maxResults, lowerLeftLatitude,
|
||||||
|
lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Address> getFromLocationNameWithOptions(String locationName, int maxResults, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, String locale, Bundle options) {
|
||||||
|
return GeocoderBackendService.this.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getInitIntent() {
|
||||||
|
return GeocoderBackendService.this.getInitIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getSettingsIntent() {
|
||||||
|
return GeocoderBackendService.this.getSettingsIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getAboutIntent() {
|
||||||
|
return GeocoderBackendService.this.getAboutIntent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
|
||||||
|
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||||
|
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public abstract class HelperLocationBackendService extends LocationBackendService {
|
||||||
|
|
||||||
|
private boolean opened;
|
||||||
|
private final Set<AbstractBackendHelper> helpers = new HashSet<>();
|
||||||
|
|
||||||
|
public synchronized void addHelper(AbstractBackendHelper helper) {
|
||||||
|
helpers.add(helper);
|
||||||
|
if (opened) {
|
||||||
|
helper.onOpen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeHelpers() {
|
||||||
|
if (opened) {
|
||||||
|
for (AbstractBackendHelper helper : helpers) {
|
||||||
|
helper.onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
helpers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected synchronized void onOpen() {
|
||||||
|
for (AbstractBackendHelper helper : helpers) {
|
||||||
|
helper.onOpen();
|
||||||
|
}
|
||||||
|
opened = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected synchronized void onClose() {
|
||||||
|
for (AbstractBackendHelper helper : helpers) {
|
||||||
|
helper.onClose();
|
||||||
|
}
|
||||||
|
opened = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected synchronized Location update() {
|
||||||
|
for (AbstractBackendHelper helper : helpers) {
|
||||||
|
helper.onUpdate();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Intent getInitIntent() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
// Consider permissions
|
||||||
|
List<String> perms = new LinkedList<>();
|
||||||
|
for (AbstractBackendHelper helper : helpers) {
|
||||||
|
perms.addAll(Arrays.asList(helper.getRequiredPermissions()));
|
||||||
|
}
|
||||||
|
// Request background location permission if needed as we are likely to run in background
|
||||||
|
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q && (perms.contains(ACCESS_COARSE_LOCATION) || perms.contains(ACCESS_FINE_LOCATION))) {
|
||||||
|
perms.add(ACCESS_BACKGROUND_LOCATION);
|
||||||
|
}
|
||||||
|
for (Iterator<String> iterator = perms.iterator(); iterator.hasNext(); ) {
|
||||||
|
String perm = iterator.next();
|
||||||
|
if (checkSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (perms.isEmpty()) return null;
|
||||||
|
Intent intent = new Intent(this, MPermissionHelperActivity.class);
|
||||||
|
intent.putExtra(MPermissionHelperActivity.EXTRA_PERMISSIONS, perms.toArray(new String[0]));
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
return super.getInitIntent();
|
||||||
|
}
|
||||||
|
}
|
121
api/src/main/java/org/microg/nlp/api/LocationBackendService.java
Normal file
121
api/src/main/java/org/microg/nlp/api/LocationBackendService.java
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
|
public abstract class LocationBackendService extends AbstractBackendService {
|
||||||
|
|
||||||
|
private LocationCallback callback;
|
||||||
|
private Location waiting;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called, whenever an app requires a location update. This can be a single or a repeated request.
|
||||||
|
* <p/>
|
||||||
|
* You may return null if your backend has no newer location available then the last one.
|
||||||
|
* Do not send the same {@link android.location.Location} twice, if it's not based on updated/refreshed data.
|
||||||
|
* <p/>
|
||||||
|
* You can completely ignore this method (means returning null) if you use {@link #report(android.location.Location)}.
|
||||||
|
*
|
||||||
|
* @return a new {@link android.location.Location} instance or null if not available.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("SameReturnValue")
|
||||||
|
protected Location update() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Location update(Bundle options) {
|
||||||
|
return update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directly report a {@link android.location.Location} to the requesting apps. Use this if your updates are based
|
||||||
|
* on environment changes (eg. cell id change).
|
||||||
|
*
|
||||||
|
* @param location the new {@link android.location.Location} instance to be send
|
||||||
|
*/
|
||||||
|
public void report(Location location) {
|
||||||
|
if (callback != null) {
|
||||||
|
try {
|
||||||
|
callback.report(location);
|
||||||
|
} catch (android.os.DeadObjectException e) {
|
||||||
|
waiting = location;
|
||||||
|
callback = null;
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
waiting = location;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
waiting = location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if we're an actively connected backend, false if not
|
||||||
|
*/
|
||||||
|
public boolean isConnected() {
|
||||||
|
return callback != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IBinder getBackend() {
|
||||||
|
return new Backend();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect() {
|
||||||
|
if (callback != null) {
|
||||||
|
onClose();
|
||||||
|
callback = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Backend extends LocationBackend.Stub {
|
||||||
|
@Override
|
||||||
|
public void open(LocationCallback callback) throws RemoteException {
|
||||||
|
LocationBackendService.this.callback = callback;
|
||||||
|
if (waiting != null) {
|
||||||
|
callback.report(waiting);
|
||||||
|
waiting = null;
|
||||||
|
}
|
||||||
|
onOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Location update() {
|
||||||
|
return LocationBackendService.this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Location updateWithOptions(Bundle options) {
|
||||||
|
return LocationBackendService.this.update(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getInitIntent() {
|
||||||
|
return LocationBackendService.this.getInitIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getSettingsIntent() {
|
||||||
|
return LocationBackendService.this.getSettingsIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getAboutIntent() {
|
||||||
|
return LocationBackendService.this.getAboutIntent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
api/src/main/java/org/microg/nlp/api/LocationHelper.java
Normal file
111
api/src/main/java/org/microg/nlp/api/LocationHelper.java
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
|
public final class LocationHelper {
|
||||||
|
public static final String EXTRA_AVERAGED_OF = "AVERAGED_OF";
|
||||||
|
public static final String EXTRA_TOTAL_WEIGHT = "org.microg.nlp.TOTAL_WEIGHT";
|
||||||
|
public static final String EXTRA_TOTAL_ALTITUDE_WEIGHT = "org.microg.nlp.TOTAL_ALTITUDE_WEIGHT";
|
||||||
|
public static final String EXTRA_WEIGHT = "org.microg.nlp.WEIGHT";
|
||||||
|
|
||||||
|
private LocationHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location create(String source) {
|
||||||
|
Location l = new Location(source);
|
||||||
|
l.setTime(System.currentTimeMillis());
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location create(String source, double latitude, double longitude, float accuracy) {
|
||||||
|
Location location = create(source);
|
||||||
|
location.setLatitude(latitude);
|
||||||
|
location.setLongitude(longitude);
|
||||||
|
location.setAccuracy(accuracy);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location create(String source, double latitude, double longitude, float altitude, Bundle extras) {
|
||||||
|
Location location = create(source, latitude, longitude, altitude);
|
||||||
|
location.setExtras(extras);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location create(String source, double latitude, double longitude, double altitude, float accuracy) {
|
||||||
|
Location location = create(source, latitude, longitude, accuracy);
|
||||||
|
location.setAltitude(altitude);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location create(String source, double latitude, double longitude, double altitude, float accuracy, Bundle extras) {
|
||||||
|
Location location = create(source, latitude, longitude, altitude, accuracy);
|
||||||
|
location.setExtras(extras);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location create(String source, long time) {
|
||||||
|
Location location = create(source);
|
||||||
|
location.setTime(time);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location create(String source, long time, Bundle extras) {
|
||||||
|
Location location = create(source, time);
|
||||||
|
location.setExtras(extras);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location average(String source, Collection<Location> locations) {
|
||||||
|
return weightedAverage(source, locations, LocationBalance.BALANCED, new Bundle());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location weightedAverage(String source, Collection<Location> locations, LocationBalance balance, Bundle extras) {
|
||||||
|
if (locations == null || locations.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
double total = 0;
|
||||||
|
double lat = 0;
|
||||||
|
double lon = 0;
|
||||||
|
float acc = 0;
|
||||||
|
double altTotal = 0;
|
||||||
|
double alt = 0;
|
||||||
|
for (Location value : locations) {
|
||||||
|
if (value != null) {
|
||||||
|
double weight = balance.getWeight(value);
|
||||||
|
total += weight;
|
||||||
|
lat += value.getLatitude() * weight;
|
||||||
|
lon += value.getLongitude() * weight;
|
||||||
|
acc += value.getAccuracy() * weight;
|
||||||
|
if (value.hasAltitude()) {
|
||||||
|
alt += value.getAltitude();
|
||||||
|
altTotal += weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (extras == null) extras = new Bundle();
|
||||||
|
extras.putInt(EXTRA_AVERAGED_OF, locations.size());
|
||||||
|
extras.putDouble(EXTRA_TOTAL_WEIGHT, total);
|
||||||
|
if (altTotal > 0) {
|
||||||
|
extras.putDouble(EXTRA_TOTAL_ALTITUDE_WEIGHT, altTotal);
|
||||||
|
return create(source, lat / total, lon / total, alt / altTotal, (float) (acc / total), extras);
|
||||||
|
} else {
|
||||||
|
return create(source, lat / total, lon / total, (float) (acc / total), extras);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface LocationBalance {
|
||||||
|
LocationBalance BALANCED = location -> 1;
|
||||||
|
LocationBalance FROM_EXTRA = location -> location.getExtras() == null ? 1 : location.getExtras().getDouble(EXTRA_WEIGHT, 1);
|
||||||
|
|
||||||
|
double getWeight(Location location);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
public class MPermissionHelperActivity extends Activity {
|
||||||
|
public static final String EXTRA_PERMISSIONS = "org.microg.nlp.api.mperms";
|
||||||
|
private static final int REQUEST_CODE_PERMS = 1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
String[] mperms = getIntent().getStringArrayExtra(EXTRA_PERMISSIONS);
|
||||||
|
if (mperms == null || mperms.length == 0) {
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
requestPermissions(mperms, REQUEST_CODE_PERMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
boolean ok = true;
|
||||||
|
for (int result : grantResults) {
|
||||||
|
if (result != PackageManager.PERMISSION_GRANTED) ok = false;
|
||||||
|
}
|
||||||
|
setResult(ok ? RESULT_OK : RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
94
api/src/main/java/org/microg/nlp/api/Utils.java
Normal file
94
api/src/main/java/org/microg/nlp/api/Utils.java
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bring a mac address to the form 01:23:45:AB:CD:EF
|
||||||
|
*
|
||||||
|
* @param mac address to be well-formed
|
||||||
|
* @return well-formed mac address
|
||||||
|
*/
|
||||||
|
public static String wellFormedMac(String mac) {
|
||||||
|
int HEX_RADIX = 16;
|
||||||
|
int[] bytes = new int[6];
|
||||||
|
String[] splitAtColon = mac.split(":");
|
||||||
|
if (splitAtColon.length == 6) {
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
bytes[i] = Integer.parseInt(splitAtColon[i], HEX_RADIX);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String[] splitAtLine = mac.split("-");
|
||||||
|
if (splitAtLine.length == 6) {
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
bytes[i] = Integer.parseInt(splitAtLine[i], HEX_RADIX);
|
||||||
|
}
|
||||||
|
} else if (mac.length() == 12) {
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
bytes[i] = Integer.parseInt(mac.substring(i * 2, (i + 1) * 2), HEX_RADIX);
|
||||||
|
}
|
||||||
|
} else if (mac.length() == 17) {
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
bytes[i] = Integer.parseInt(mac.substring(i * 3, (i * 3) + 2), HEX_RADIX);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Can't read this string as mac address");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
String hex = Integer.toHexString(bytes[i]);
|
||||||
|
if (hex.length() == 1) {
|
||||||
|
hex = "0" + hex;
|
||||||
|
}
|
||||||
|
if (sb.length() != 0)
|
||||||
|
sb.append(":");
|
||||||
|
sb.append(hex);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPackageApiVersion(Context context, String packageName) {
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
ApplicationInfo applicationInfo;
|
||||||
|
try {
|
||||||
|
applicationInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return applicationInfo.metaData == null ? null
|
||||||
|
: applicationInfo.metaData.getString(Constants.METADATA_API_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getServiceApiVersion(Context context) {
|
||||||
|
String apiVersion = getPackageApiVersion(context, "com.google.android.gms");
|
||||||
|
if (apiVersion == null)
|
||||||
|
apiVersion = getPackageApiVersion(context, "com.google.android.location");
|
||||||
|
if (apiVersion == null) apiVersion = getPackageApiVersion(context, "org.microg.nlp");
|
||||||
|
return apiVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSelfApiVersion(Context context) {
|
||||||
|
String apiVersion = getPackageApiVersion(context, context.getPackageName());
|
||||||
|
if (!Constants.API_VERSION.equals(apiVersion)) {
|
||||||
|
Log.w("VersionUtil", "You did not specify the currently used api version in your manifest.\n" +
|
||||||
|
"When using gradle + aar, this should be done automatically, if not, add the\n" +
|
||||||
|
"following to your <application> tag\n" +
|
||||||
|
"<meta-data android:name=\"" + Constants.METADATA_API_VERSION +
|
||||||
|
"\" android:value=\"" + Constants.API_VERSION + "\" />");
|
||||||
|
apiVersion = Constants.API_VERSION;
|
||||||
|
}
|
||||||
|
return apiVersion;
|
||||||
|
}
|
||||||
|
}
|
25
api/src/main/java/org/microg/nlp/api/VersionUtils.java
Normal file
25
api/src/main/java/org/microg/nlp/api/VersionUtils.java
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Deprecated
|
||||||
|
public class VersionUtils {
|
||||||
|
|
||||||
|
public static String getPackageApiVersion(Context context, String packageName) {
|
||||||
|
return Utils.getPackageApiVersion(context, packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getServiceApiVersion(Context context) {
|
||||||
|
return Utils.getServiceApiVersion(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSelfApiVersion(Context context) {
|
||||||
|
return Utils.getSelfApiVersion(context);
|
||||||
|
}
|
||||||
|
}
|
241
api/src/main/java/org/microg/nlp/api/WiFiBackendHelper.java
Normal file
241
api/src/main/java/org/microg/nlp/api/WiFiBackendHelper.java
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.microg.nlp.api;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.net.wifi.ScanResult;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||||
|
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||||
|
import static android.Manifest.permission.ACCESS_WIFI_STATE;
|
||||||
|
import static android.Manifest.permission.CHANGE_WIFI_STATE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to support backends that use Wi-Fis for geolocation.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"MissingPermission", "WeakerAccess", "unused"})
|
||||||
|
public class WiFiBackendHelper extends AbstractBackendHelper {
|
||||||
|
private final static IntentFilter wifiBroadcastFilter =
|
||||||
|
new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
|
||||||
|
|
||||||
|
private final Listener listener;
|
||||||
|
private final WifiManager wifiManager;
|
||||||
|
private final Set<WiFi> wiFis = new HashSet<>();
|
||||||
|
private final BroadcastReceiver wifiBroadcastReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
onWiFisChanged();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private boolean ignoreNomap = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of {@link WiFiBackendHelper}. Call this in
|
||||||
|
* {@link LocationBackendService#onCreate()}.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if either context or listener is null.
|
||||||
|
*/
|
||||||
|
public WiFiBackendHelper(Context context, Listener listener) {
|
||||||
|
super(context);
|
||||||
|
if (listener == null)
|
||||||
|
throw new IllegalArgumentException("listener must not be null");
|
||||||
|
this.listener = listener;
|
||||||
|
this.wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether to ignore the "_nomap" flag on Wi-Fi SSIDs or not.
|
||||||
|
* <p/>
|
||||||
|
* Usually, Wi-Fis whose SSID end with "_nomap" are ignored for geolocation. This behaviour can
|
||||||
|
* be suppressed by {@code setIgnoreNomap(false)}.
|
||||||
|
* <p/>
|
||||||
|
* Default is {@code true}.
|
||||||
|
*/
|
||||||
|
public void setIgnoreNomap(boolean ignoreNomap) {
|
||||||
|
this.ignoreNomap = ignoreNomap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this in {@link LocationBackendService#onOpen()}.
|
||||||
|
*/
|
||||||
|
public synchronized void onOpen() {
|
||||||
|
super.onOpen();
|
||||||
|
context.registerReceiver(wifiBroadcastReceiver, wifiBroadcastFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this in {@link LocationBackendService#onClose()}.
|
||||||
|
*/
|
||||||
|
public synchronized void onClose() {
|
||||||
|
super.onClose();
|
||||||
|
context.unregisterReceiver(wifiBroadcastReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this in {@link LocationBackendService#update()}.
|
||||||
|
*/
|
||||||
|
public synchronized void onUpdate() {
|
||||||
|
if (!currentDataUsed) {
|
||||||
|
listener.onWiFisChanged(getWiFis());
|
||||||
|
} else {
|
||||||
|
scanWiFis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getRequiredPermissions() {
|
||||||
|
return new String[]{CHANGE_WIFI_STATE, ACCESS_WIFI_STATE, ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onWiFisChanged() {
|
||||||
|
if (loadWiFis()) {
|
||||||
|
listener.onWiFisChanged(getWiFis());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private synchronized boolean scanWiFis() {
|
||||||
|
if (state == State.DISABLED)
|
||||||
|
return false;
|
||||||
|
if (wifiManager.isWifiEnabled() || isScanAlwaysAvailable()) {
|
||||||
|
state = State.SCANNING;
|
||||||
|
wifiManager.startScan();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private boolean isScanAlwaysAvailable() {
|
||||||
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
|
||||||
|
&& wifiManager.isScanAlwaysAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized boolean loadWiFis() {
|
||||||
|
int oldHash = wiFis.hashCode();
|
||||||
|
wiFis.clear();
|
||||||
|
currentDataUsed = false;
|
||||||
|
List<ScanResult> scanResults = wifiManager.getScanResults();
|
||||||
|
for (ScanResult scanResult : scanResults) {
|
||||||
|
if (ignoreNomap && scanResult.SSID.toLowerCase(Locale.US).endsWith("_nomap")) continue;
|
||||||
|
wiFis.add(new WiFi(scanResult.BSSID, scanResult.level, frequencyToChannel(scanResult.frequency), scanResult.frequency));
|
||||||
|
}
|
||||||
|
if (state == State.DISABLING)
|
||||||
|
state = State.DISABLED;
|
||||||
|
switch (state) {
|
||||||
|
default:
|
||||||
|
case DISABLED:
|
||||||
|
return false;
|
||||||
|
case SCANNING:
|
||||||
|
state = State.WAITING;
|
||||||
|
return wiFis.hashCode() != oldHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("MagicNumber")
|
||||||
|
private static int frequencyToChannel(int freq) {
|
||||||
|
if (freq >= 2412 && freq <= 2484) {
|
||||||
|
return (freq - 2412) / 5 + 1;
|
||||||
|
} else if (freq >= 5170 && freq <= 5825) {
|
||||||
|
return (freq - 5170) / 5 + 34;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the latest scan result.
|
||||||
|
*/
|
||||||
|
public synchronized Set<WiFi> getWiFis() {
|
||||||
|
currentDataUsed = true;
|
||||||
|
return new HashSet<>(wiFis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to listen for Wi-Fi scan results.
|
||||||
|
*/
|
||||||
|
public interface Listener {
|
||||||
|
/**
|
||||||
|
* Called when a new set of Wi-Fi's is discovered.
|
||||||
|
*/
|
||||||
|
void onWiFisChanged(Set<WiFi> wiFis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a generic Wi-Fi scan result.
|
||||||
|
* <p/>
|
||||||
|
* This does contain the BSSID (mac address) and the RSSI (in dBm) of a Wi-Fi.
|
||||||
|
* Additional data is not provided, but also not usable for geolocation.
|
||||||
|
*/
|
||||||
|
public static class WiFi {
|
||||||
|
private final String bssid;
|
||||||
|
private final int rssi;
|
||||||
|
private final int channel;
|
||||||
|
private final int frequency;
|
||||||
|
|
||||||
|
public String getBssid() {
|
||||||
|
return bssid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRssi() {
|
||||||
|
return rssi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getChannel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFrequency() {
|
||||||
|
return frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WiFi(String bssid, int rssi) {
|
||||||
|
this(bssid, rssi, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WiFi(String bssid, int rssi, Integer channel, Integer frequency) {
|
||||||
|
this.bssid = Utils.wellFormedMac(bssid);
|
||||||
|
this.rssi = rssi;
|
||||||
|
this.channel = channel;
|
||||||
|
this.frequency = frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
WiFi wiFi = (WiFi) o;
|
||||||
|
|
||||||
|
if (rssi != wiFi.rssi) return false;
|
||||||
|
if (channel != wiFi.channel) return false;
|
||||||
|
if (frequency != wiFi.frequency) return false;
|
||||||
|
return bssid != null ? bssid.equals(wiFi.bssid) : wiFi.bssid == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = bssid != null ? bssid.hashCode() : 0;
|
||||||
|
result = 31 * result + rssi;
|
||||||
|
result = 31 * result + channel;
|
||||||
|
result = 31 * result + frequency;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
api/src/main/res/values/version.xml
Normal file
9
api/src/main/res/values/version.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ SPDX-FileCopyrightText: 2013, microG Project Team
|
||||||
|
~ SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string name="nlp_api_version">3</string>
|
||||||
|
</resources>
|
Loading…
Reference in a new issue