Add API
This commit is contained in:
parent
18b16dcc1f
commit
4b5b3251ae
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 New Issue