Major cleanup, Add support for Geocoder

Using spaces for indentation from now on
Add support for forced locations (debug)
This commit is contained in:
mar-v-in 2014-12-25 12:49:33 +01:00
parent 6606651255
commit 8ff926120c
30 changed files with 1693 additions and 1428 deletions

View file

@ -18,7 +18,6 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, api/src)
# For some reason framework has to be added here else GeocoderParams is not found,
# this way everything else is duplicated, but atleast compiles...
@ -81,5 +80,5 @@ LOCAL_AAPT_FLAGS := --rename-manifest-package com.google.android.location
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
include $(LOCAL_PATH)/api/Android.mk

View file

@ -1,94 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="org.microg.nlp"
android:versionName="1.0.1"
android:versionCode="1001"
xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.microg.nlp"
android:versionName="1.1.0"
android:versionCode="1100">
<uses-sdk android:minSdkVersion="14" />
<uses-sdk
android:minSdkVersion="14" />
<uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" />
<uses-permission android:name="android.permission.ACCESS_COARSE_UPDATES" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<permission
android:name="org.microg.permission.FORCE_COARSE_LOCATION"
android:protectionLevel="dangerous" />
<application
android:icon="@drawable/nlp_app_icon"
android:label="@string/nlp_app_name">
<uses-library android:name="com.android.location.provider" />
<uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" />
<uses-permission android:name="android.permission.ACCESS_COARSE_UPDATES" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="org.microg.permission.FORCE_COARSE_LOCATION" />
<!-- Gingerbread / Ice Cream Sandwich -->
<service
android:name="org.microg.nlp.location.LocationServiceV1"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.location.NetworkLocationProvider" />
</intent-filter>
<application
android:icon="@drawable/nlp_app_icon"
android:label="@string/nlp_app_name">
<uses-library android:name="com.android.location.provider" />
<meta-data
android:name="serviceVersion"
android:value="1" />
<meta-data
android:name="version"
android:value="1" />
</service>
<!-- Gingerbread / Ice Cream Sandwich -->
<service
android:name="org.microg.nlp.location.LocationServiceV1"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.location.NetworkLocationProvider" />
</intent-filter>
<!-- Jelly Bean / KitKat -->
<service
android:name="org.microg.nlp.location.LocationServiceV2"
android:exported="true">
<intent-filter>
<!-- KitKat changed the action name but nothing else, hence we handle it the same -->
<action android:name="com.android.location.service.v3.NetworkLocationProvider" />
<action android:name="com.android.location.service.v2.NetworkLocationProvider" />
</intent-filter>
<meta-data
android:name="serviceVersion"
android:value="1" />
<meta-data
android:name="version"
android:value="1" />
</service>
<meta-data
android:name="serviceVersion"
android:value="2" />
<meta-data
android:name="serviceIsMultiuser"
android:value="false" />
</service>
<!-- Jelly Bean / KitKat -->
<service
android:name="org.microg.nlp.location.LocationServiceV2"
android:exported="true">
<intent-filter>
<!-- KitKat changed the action name but nothing else, hence we handle it the same -->
<action android:name="com.android.location.service.v3.NetworkLocationProvider" />
<action android:name="com.android.location.service.v2.NetworkLocationProvider" />
</intent-filter>
<service
android:name="org.microg.nlp.geocode.GeocodeServiceV1"
android:exported="true">
<intent-filter>
<!-- Jelly Bean changed the action name but nothing else, hence we handle it the same -->
<action android:name="com.android.location.service.GeocodeProvider" />
<action android:name="com.google.android.location.GeocodeProvider" />
</intent-filter>
<meta-data
android:name="serviceVersion"
android:value="2" />
<meta-data
android:name="serviceIsMultiuser"
android:value="false" />
</service>
<meta-data
android:name="serviceVersion"
android:value="2" />
<meta-data
android:name="serviceIsMultiuser"
android:value="false" />
</service>
<service
android:name="org.microg.nlp.geocode.GeocodeServiceV1"
android:exported="true">
<intent-filter>
<!-- Jelly Bean changed the action name but nothing else, hence we handle it the same -->
<action android:name="com.android.location.service.GeocodeProvider" />
<action android:name="com.google.android.location.GeocodeProvider" />
</intent-filter>
<activity
android:name="org.microg.nlp.ui.LocationBackendConfig"
android:theme="@android:style/Theme.Holo.Light">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="serviceVersion"
android:value="2" />
<meta-data
android:name="serviceIsMultiuser"
android:value="false" />
</service>
<receiver android:name="org.microg.nlp.PackageReceiver" >
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_CHANGED" />
<action android:name="android.intent.action.PACKAGE_UPGRADED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_RESTARTED" />
<activity
android:name="org.microg.nlp.ui.LocationBackendConfig"
android:theme="@android:style/Theme.Light">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<data android:scheme="package" />
</intent-filter>
</receiver>
</application>
<receiver android:name="org.microg.nlp.PackageReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_CHANGED" />
<action android:name="android.intent.action.PACKAGE_UPGRADED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_RESTARTED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
</application>
</manifest>

View file

@ -4,6 +4,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE := UnifiedNlpApi
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += src/org/microg/nlp/api/LocationBackend.aidl \
src/org/microg/nlp/api/GeocoderBackend.aidl \
src/org/microg/nlp/api/LocationCallback.aidl
include $(BUILD_STATIC_JAVA_LIBRARY)

View file

@ -0,0 +1,13 @@
package org.microg.nlp.api;
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, double lowerLeftLatitude,
double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude,
int maxResults, String locale);
void close();
}

View file

@ -4,7 +4,7 @@ import org.microg.nlp.api.LocationCallback;
import android.location.Location;
interface LocationBackend {
void open(LocationCallback callback);
Location update();
void close();
void open(LocationCallback callback);
Location update();
void close();
}

View file

@ -8,91 +8,91 @@ import android.os.RemoteException;
public abstract class LocationBackendService extends Service {
private Backend backend = new Backend();
private LocationCallback callback;
private Location waiting;
private Backend backend = new Backend();
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.
*/
protected Location update() {
return null;
}
/**
* 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.
*/
protected Location update() {
return null;
}
/**
* 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;
}
}
/**
* 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;
}
}
@Override
public IBinder onBind(Intent intent) {
return backend;
}
@Override
public IBinder onBind(Intent intent) {
return backend;
}
/**
* Called after a connection was setup
*/
protected void onOpen() {
/**
* Called after a connection was setup
*/
protected void onOpen() {
}
}
/**
* Called before connection closure
*/
protected void onClose() {
/**
* Called before connection closure
*/
protected void onClose() {
}
}
/**
* @return true if we're an actively connected backend, false if not
*/
public boolean isConnected() {
return callback != null;
}
/**
* @return true if we're an actively connected backend, false if not
*/
public boolean isConnected() {
return 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();
}
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() throws RemoteException {
return LocationBackendService.this.update();
}
@Override
public Location update() throws RemoteException {
return LocationBackendService.this.update();
}
@Override
public void close() throws RemoteException {
onClose();
callback = null;
}
}
@Override
public void close() throws RemoteException {
onClose();
callback = null;
}
}
}

View file

@ -3,5 +3,5 @@ package org.microg.nlp.api;
import android.location.Location;
interface LocationCallback {
void report(in Location location);
void report(in Location location);
}

View file

@ -6,78 +6,83 @@ import android.os.Bundle;
import java.util.Collection;
public final class LocationHelper {
private LocationHelper() {
}
private LocationHelper() {
}
public static Location create(String source) {
return new Location(source);
}
public static Location create(String source) {
return new Location(source);
}
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 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, 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) {
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, 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) {
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 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) {
if (locations == null || locations.size() == 0) {
return null;
}
int num = locations.size();
double latitude = 0;
double longitude = 0;
float accuracy = 0;
int altitudes = 0;
double altitude = 0;
for (Location value : locations) {
if (value != null) {
latitude += value.getLatitude();
longitude += value.getLongitude();
accuracy += value.getAccuracy();
if (value.hasAltitude()) {
altitude += value.getAltitude();
altitudes++;
}
}
}
Bundle extras = new Bundle();
extras.putInt("AVERAGED_OF", num);
if (altitudes > 0) {
return create(source, latitude / num, longitude / num, altitude / altitudes, accuracy / num, extras);
} else {
return create(source, latitude / num, longitude / num, accuracy / num, extras);
}
}
public static Location average(String source, Collection<Location> locations) {
if (locations == null || locations.size() == 0) {
return null;
}
int num = locations.size();
double latitude = 0;
double longitude = 0;
float accuracy = 0;
int altitudes = 0;
double altitude = 0;
for (Location value : locations) {
if (value != null) {
latitude += value.getLatitude();
longitude += value.getLongitude();
accuracy += value.getAccuracy();
if (value.hasAltitude()) {
altitude += value.getAltitude();
altitudes++;
}
}
}
Bundle extras = new Bundle();
extras.putInt("AVERAGED_OF", num);
if (altitudes > 0) {
return create(source, latitude / num, longitude / num, altitude / altitudes,
accuracy / num, extras);
} else {
return create(source, latitude / num, longitude / num, accuracy / num, extras);
}
}
}

View file

@ -1,6 +1,14 @@
package org.microg.nlp.api;
public class NlpApiConstants {
public static final String ACTION_LOCATION_BACKEND = "org.microg.nlp.LOCATION_BACKEND";
public static final String METADATA_BACKEND_SETTINGS_ACTIVITY = "org.microg.nlp.BACKEND_SETTINGS_ACTIVITY";
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";
}

View file

@ -0,0 +1,55 @@
package org.microg.nlp;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.RemoteException;
import android.util.Log;
public abstract class AbstractBackendHelper implements ServiceConnection {
protected final Context context;
protected final Intent serviceIntent;
protected boolean bound;
private final String TAG;
public AbstractBackendHelper(String tag, Context context, Intent serviceIntent) {
TAG = tag;
this.context = context;
this.serviceIntent = serviceIntent;
}
public abstract void close() throws RemoteException;
public abstract boolean hasBackend();
public void unbind() {
if (bound) {
if (hasBackend()) {
try {
close();
} catch (Exception e) {
Log.w(TAG, e);
}
}
try {
context.unbindService(this);
} catch (Exception e) {
Log.w(TAG, e);
}
bound = false;
}
}
public boolean bind() {
if (!bound) {
try {
context.bindService(serviceIntent, this, Context.BIND_AUTO_CREATE);
} catch (Exception e) {
Log.w(TAG, e);
return false;
}
}
return true;
}
}

View file

@ -7,11 +7,12 @@ import android.util.Log;
import org.microg.nlp.location.LocationService;
public class PackageReceiver extends BroadcastReceiver {
private static final String TAG = PackageReceiver.class.getName();
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Intent received: "+intent);
Log.d(TAG, "Reloading location service...");
LocationService.reloadLocationService(context);
}
private static final String TAG = PackageReceiver.class.getName();
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Intent received: " + intent);
Log.d(TAG, "Reloading location service...");
LocationService.reloadLocationService(context);
}
}

View file

@ -0,0 +1,37 @@
package org.microg.nlp;
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings;
public class Preferences {
private static final String DEFAULT_LOCATION_BACKENDS = "default_location_backends";
private static final String LOCATION_BACKENDS = "location_backends";
private static final String DEFAULT_GEOCODER_BACKENDS = "default_geocoder_backends";
private static final String GEOCODER_BACKENDS = "geocoder_backends";
private final Context context;
public Preferences(Context context) {
this.context = context;
}
private SharedPreferences getSharedPreferences() {
return context.getSharedPreferences("config", Context.MODE_PRIVATE);
}
public String[] getLocationBackends() {
String defBackends = Settings.System.getString(context.getContentResolver(), DEFAULT_LOCATION_BACKENDS);
String backends = getSharedPreferences().getString(LOCATION_BACKENDS, defBackends);
return backends.split("\\|");
}
public void setLocationBackends(String backends) {
getSharedPreferences().edit().putString(LOCATION_BACKENDS, backends).commit();
}
public String[] getGeocoderBackends() {
String defBackends = Settings.System.getString(context.getContentResolver(), DEFAULT_GEOCODER_BACKENDS);
String backends = getSharedPreferences().getString(GEOCODER_BACKENDS, defBackends);
return backends.split("\\|");
}
}

View file

@ -3,7 +3,7 @@ package org.microg.nlp;
import android.os.IBinder;
public interface Provider {
IBinder getBinder();
IBinder getBinder();
void reload();
void reload();
}

View file

@ -4,51 +4,51 @@ import android.app.IntentService;
import android.content.Intent;
import android.os.IBinder;
public abstract class ProviderService extends IntentService {
private Provider provider;
public abstract class ProviderService<T extends Provider> extends IntentService {
private T provider;
/**
* Creates an ProviderService. Invoked by your subclass's constructor.
*
* @param tag Used for debugging.
*/
public ProviderService(String tag) {
super(tag);
}
/**
* Creates an ProviderService. Invoked by your subclass's constructor.
*
* @param tag Used for debugging.
*/
public ProviderService(String tag) {
super(tag);
}
@Override
public void onCreate() {
super.onCreate();
provider = createProvider();
}
@Override
public void onCreate() {
super.onCreate();
provider = createProvider();
}
@Override
public IBinder onBind(Intent intent) {
return provider.getBinder();
}
@Override
public IBinder onBind(Intent intent) {
return provider.getBinder();
}
@Override
public void onDestroy() {
provider = null;
}
@Override
public void onDestroy() {
provider = null;
}
/**
* Create a {@link org.microg.nlp.Provider}.
* This is most likely only called once
*
* @return a new {@link org.microg.nlp.Provider} instance
*/
protected abstract Provider createProvider();
/**
* Create a {@link org.microg.nlp.Provider}.
* This is most likely only called once
*
* @return a new {@link org.microg.nlp.Provider} instance
*/
protected abstract T createProvider();
@Override
protected void onHandleIntent(Intent intent) {
// Default implementation is to do nothing
}
@Override
protected void onHandleIntent(Intent intent) {
// Default implementation is to do nothing
}
/**
* @return the currently used {@link org.microg.nlp.Provider} instance
*/
protected Provider getCurrentProvider() {
return provider;
}
/**
* @return the currently used {@link org.microg.nlp.Provider} instance
*/
protected T getCurrentProvider() {
return provider;
}
}

View file

@ -0,0 +1,60 @@
package org.microg.nlp.geocode;
import android.content.Context;
import android.content.Intent;
import android.location.Address;
import org.microg.nlp.Preferences;
import java.util.ArrayList;
import java.util.List;
import static org.microg.nlp.api.NlpApiConstants.ACTION_GEOCODER_BACKEND;
class BackendFuser {
private final List<BackendHelper> backendHelpers;
public BackendFuser(Context context) {
backendHelpers = new ArrayList<BackendHelper>();
for (String backend : new Preferences(context).getGeocoderBackends()) {
String[] parts = backend.split("/");
if (parts.length == 2) {
Intent intent = new Intent(ACTION_GEOCODER_BACKEND);
intent.setPackage(parts[0]);
intent.setClassName(parts[0], parts[1]);
backendHelpers.add(new BackendHelper(context, intent));
}
}
}
public List<Address> getFromLocation(double latitude, double longitude, int maxResults,
String locale) {
if (backendHelpers.isEmpty())
return null;
ArrayList<Address> result = new ArrayList<Address>();
for (BackendHelper backendHelper : backendHelpers) {
List<Address> backendResult = backendHelper
.getFromLocation(latitude, longitude, maxResults, locale);
if (backendResult != null) {
result.addAll(backendResult);
}
}
return result;
}
public List<Address> getFromLocationName(String locationName, double lowerLeftLatitude,
double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude,
int maxResults, String locale) {
if (backendHelpers.isEmpty())
return null;
ArrayList<Address> result = new ArrayList<Address>();
for (BackendHelper backendHelper : backendHelpers) {
List<Address> backendResult = backendHelper.getFromLocationName(
locationName, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude,
upperRightLongitude, maxResults, locale);
if (backendResult != null) {
result.addAll(backendResult);
}
}
return result;
}
}

View file

@ -0,0 +1,76 @@
package org.microg.nlp.geocode;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.location.Address;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import org.microg.nlp.AbstractBackendHelper;
import org.microg.nlp.api.GeocoderBackend;
import java.util.List;
class BackendHelper extends AbstractBackendHelper {
private static final String TAG = BackendHelper.class.getName();
private GeocoderBackend backend;
public BackendHelper(Context context, Intent serviceIntent) {
super(TAG, context, serviceIntent);
}
public List<Address> getFromLocation(double latitude, double longitude, int maxResults,
String locale) {
try {
return backend.getFromLocation(latitude, longitude, maxResults, locale);
} catch (Exception e) {
Log.w(TAG, e);
unbind();
return null;
}
}
public List<Address> getFromLocationName(String locationName, double lowerLeftLatitude,
double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude,
int maxResults, String locale) {
try {
return backend.getFromLocationName(locationName, lowerLeftLatitude, lowerLeftLongitude,
upperRightLatitude, upperRightLongitude, maxResults, locale);
} catch (Exception e) {
Log.w(TAG, e);
unbind();
return null;
}
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bound = true;
backend = GeocoderBackend.Stub.asInterface(service);
if (backend != null) {
try {
backend.open();
} catch (Exception e) {
Log.w(TAG, e);
unbind();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
backend = null;
bound = false;
}
@Override
public void close() throws RemoteException {
backend.close();
}
@Override
public boolean hasBackend() {
return backend != null;
}
}

View file

@ -2,5 +2,5 @@ package org.microg.nlp.geocode;
import org.microg.nlp.Provider;
public interface GeocodeProvider extends Provider {
interface GeocodeProvider extends Provider {
}

View file

@ -1,27 +1,52 @@
package org.microg.nlp.geocode;
import android.content.Context;
import android.location.Address;
import android.location.GeocoderParams;
import com.android.location.provider.GeocodeProvider;
import java.util.List;
public class GeocodeProviderV1 extends GeocodeProvider implements org.microg.nlp.geocode.GeocodeProvider {
@Override
public String onGetFromLocation(double latitude, double longitude, int maxResults, GeocoderParams params,
List<Address> addresses) {
return null;
}
class GeocodeProviderV1 extends GeocodeProvider implements org.microg.nlp.geocode.GeocodeProvider {
private BackendFuser backendFuser;
private Context context;
@Override
public String onGetFromLocationName(String locationName, double lowerLeftLatitude, double lowerLeftLongitude,
double upperRightLatitude, double upperRightLongitude, int maxResults,
GeocoderParams params, List<Address> addresses) {
return null;
}
public GeocodeProviderV1(Context context) {
this.context = context;
reload();
}
@Override
public void reload() {
@Override
public String onGetFromLocation(double latitude, double longitude, int maxResults,
GeocoderParams params, List<Address> addresses) {
List<Address> fuserResult = backendFuser
.getFromLocation(latitude, longitude, maxResults, params.getLocale().toString());
return handleResult(addresses, fuserResult);
}
}
@Override
public String onGetFromLocationName(String locationName, double lowerLeftLatitude,
double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude,
int maxResults, GeocoderParams params, List<Address> addresses) {
List<Address> fuserResult = backendFuser.getFromLocationName(locationName,
lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
maxResults, params.getLocale().toString());
return handleResult(addresses, fuserResult);
}
private String handleResult(List<Address> realResult, List<Address> fuserResult) {
if (fuserResult == null) {
return "no backend";
} else if (fuserResult.isEmpty()) {
return "no result";
} else {
realResult.addAll(fuserResult);
return null;
}
}
@Override
public void reload() {
backendFuser = new BackendFuser(context);
}
}

View file

@ -2,13 +2,13 @@ package org.microg.nlp.geocode;
import org.microg.nlp.ProviderService;
public abstract class GeocodeService extends ProviderService {
/**
* Creates an GeocodeService. Invoked by your subclass's constructor.
*
* @param tag Used for debugging.
*/
public GeocodeService(String tag) {
super(tag);
}
public abstract class GeocodeService extends ProviderService<GeocodeProvider> {
/**
* Creates an GeocodeService. Invoked by your subclass's constructor.
*
* @param tag Used for debugging.
*/
public GeocodeService(String tag) {
super(tag);
}
}

View file

@ -1,16 +1,14 @@
package org.microg.nlp.geocode;
import org.microg.nlp.Provider;
public class GeocodeServiceV1 extends GeocodeService {
private static final String TAG = GeocodeServiceV1.class.getName();
private static final String TAG = GeocodeServiceV1.class.getName();
public GeocodeServiceV1() {
super(TAG);
}
public GeocodeServiceV1() {
super(TAG);
}
@Override
protected Provider createProvider() {
return new GeocodeProviderV1();
}
@Override
protected GeocodeProvider createProvider() {
return new GeocodeProviderV1(this);
}
}

View file

@ -3,147 +3,156 @@ package org.microg.nlp.location;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.os.IBinder;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import org.microg.nlp.api.NlpApiConstants;
import org.microg.nlp.Preferences;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class BackendFuser implements BackendHandler, LocationProvider {
private static final String TAG = BackendFuser.class.getName();
import static org.microg.nlp.api.NlpApiConstants.*;
private final List<BackendHandler> backendHandlers;
private final LocationProvider locationProvider;
private boolean fusing = false;
class BackendFuser {
private static final String TAG = BackendFuser.class.getName();
public BackendFuser(Context context, List<BackendInfo> backends, LocationProvider provider) {
locationProvider = provider;
backendHandlers = new ArrayList<BackendHandler>();
for (BackendInfo backendInfo : backends) {
Intent intent = new Intent(NlpApiConstants.ACTION_LOCATION_BACKEND);
intent.setPackage(backendInfo.packageName);
intent.setClassName(backendInfo.packageName, backendInfo.className);
backendHandlers.add(new BackendHelper(context, this, intent));
}
}
private final List<BackendHelper> backendHelpers;
private final LocationProvider locationProvider;
private Location forcedLocation;
private boolean fusing = false;
@Override
public void unbind() {
for (BackendHandler handler : backendHandlers) {
handler.unbind();
}
}
public BackendFuser(Context context, LocationProvider provider) {
locationProvider = provider;
backendHelpers = new ArrayList<BackendHelper>();
for (String backend : new Preferences(context).getLocationBackends()) {
String[] parts = backend.split("/");
if (parts.length == 2) {
Intent intent = new Intent(ACTION_LOCATION_BACKEND);
intent.setPackage(parts[0]);
intent.setClassName(parts[0], parts[1]);
backendHelpers.add(new BackendHelper(context, this, intent));
}
}
}
@Override
public boolean bind() {
fusing = false;
boolean handlerBound = false;
for (BackendHandler handler : backendHandlers) {
if (handler.bind()) {
handlerBound = true;
}
}
return handlerBound;
}
public void unbind() {
for (BackendHelper handler : backendHelpers) {
handler.unbind();
}
}
@Override
public Location update() {
fusing = true;
for (BackendHandler handler : backendHandlers) {
handler.update();
}
fusing = false;
return getLastLocation();
}
public boolean bind() {
fusing = false;
boolean handlerBound = false;
for (BackendHelper handler : backendHelpers) {
if (handler.bind()) {
handlerBound = true;
}
}
return handlerBound;
}
@Override
public Location getLastLocation() {
List<Location> locations = new ArrayList<Location>();
for (BackendHandler handler : backendHandlers) {
locations.add(handler.getLastLocation());
}
if (locations.isEmpty()) {
return null;
} else {
Collections.sort(locations, LocationComparator.INSTANCE);
if (locations.get(0) != null) {
locationProvider.reportLocation(locations.get(0));
Log.v(TAG, "location=" + locations.get(0));
}
return locations.get(0);
}
}
public Location update() {
fusing = true;
for (BackendHelper handler : backendHelpers) {
handler.update();
}
fusing = false;
return getLastLocation();
}
@Override
public void onEnable() {
locationProvider.onEnable();
}
public Location getLastLocation() {
List<Location> locations = new ArrayList<Location>();
for (BackendHelper handler : backendHelpers) {
locations.add(handler.getLastLocation());
}
if (forcedLocation != null) {
locations.add(forcedLocation);
}
if (locations.isEmpty()) {
return null;
} else {
Location location = mergeLocations(locations);
if (location != null) {
location.setProvider(LocationManager.NETWORK_PROVIDER);
locationProvider.reportLocation(location);
Log.v(TAG, "location=" + location);
}
return location;
}
}
@Override
public void onDisable() {
locationProvider.onDisable();
}
private Location mergeLocations(List<Location> locations) {
Collections.sort(locations, LocationComparator.INSTANCE);
if (locations.isEmpty() || locations.get(0) == null)
return null;
if (locations.size() == 1)
return locations.get(0);
Location location = new Location(locations.get(0));
ArrayList<Location> backendResults = new ArrayList<Location>();
for (Location backendResult : locations) {
if (locations.get(0) == backendResult)
continue;
if (backendResult != null)
backendResults.add(backendResult);
}
if (!backendResults.isEmpty()) {
location.getExtras()
.putParcelableArrayList(LOCATION_EXTRA_OTHER_BACKENDS, backendResults);
}
return location;
}
@Override
public void reportLocation(Location location) {
if (fusing) return;
getLastLocation();
}
public void reportLocation() {
if (fusing)
return;
getLastLocation();
}
@Override
public IBinder getBinder() {
return locationProvider.getBinder();
}
public void forceLocation(Location location) {
forcedLocation = location;
Bundle extras = new Bundle();
extras.putString(LOCATION_EXTRA_BACKEND_PROVIDER, "forced");
location.setExtras(extras);
}
@Override
public void reload() {
locationProvider.reload();
}
public Location getForcedLocation() {
return forcedLocation;
}
public static class BackendInfo {
private final String packageName;
private final String className;
public static class LocationComparator implements Comparator<Location> {
public BackendInfo(String packageName, String className) {
this.packageName = packageName;
this.className = className;
}
}
public static final LocationComparator INSTANCE = new LocationComparator();
public static final long SWITCH_ON_FRESHNESS_CLIFF_MS = 30000; // 30 seconds TODO: make it a setting
public static class LocationComparator implements Comparator<Location> {
public static final LocationComparator INSTANCE = new LocationComparator();
public static final long SWITCH_ON_FRESHNESS_CLIFF_MS = 30000; // 30 seconds TODO: make it a setting
/**
* @return whether {@param lhs} is better than {@param rhs}
*/
@Override
public int compare(Location lhs, Location rhs) {
if (lhs == rhs)
return 0;
if (lhs == null) {
return 1;
}
if (rhs == null) {
return -1;
}
if (!lhs.hasAccuracy()) {
return 1;
}
if (!rhs.hasAccuracy()) {
return -1;
}
if (rhs.getTime() > lhs.getTime() + SWITCH_ON_FRESHNESS_CLIFF_MS) {
return 1;
}
if (lhs.getTime() > rhs.getTime() + SWITCH_ON_FRESHNESS_CLIFF_MS) {
return -1;
}
return (int) (lhs.getAccuracy() - rhs.getAccuracy());
}
}
/**
* @return whether {@param lhs} is better than {@param rhs}
*/
@Override
public int compare(Location lhs, Location rhs) {
if (lhs == rhs)
return 0;
if (lhs == null) {
return 1;
}
if (rhs == null) {
return -1;
}
if (!lhs.hasAccuracy()) {
return 1;
}
if (!rhs.hasAccuracy()) {
return -1;
}
if (rhs.getTime() > lhs.getTime() + SWITCH_ON_FRESHNESS_CLIFF_MS) {
return 1;
}
if (lhs.getTime() > rhs.getTime() + SWITCH_ON_FRESHNESS_CLIFF_MS) {
return -1;
}
return (int) (lhs.getAccuracy() - rhs.getAccuracy());
}
}
}

View file

@ -1,13 +0,0 @@
package org.microg.nlp.location;
import android.location.Location;
public interface BackendHandler {
void unbind();
boolean bind();
Location update();
Location getLastLocation();
}

View file

@ -3,145 +3,119 @@ package org.microg.nlp.location;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.location.Location;
import android.os.*;
import android.util.Log;
import com.android.location.provider.LocationProviderBase;
import org.microg.nlp.AbstractBackendHelper;
import org.microg.nlp.api.LocationBackend;
import org.microg.nlp.api.LocationCallback;
public class BackendHelper implements BackendHandler {
private static final String TAG = BackendHelper.class.getName();
private final Context context;
private final LocationProvider provider;
private final Intent serviceIntent;
private final Connection connection = new Connection();
private final Callback callback = new Callback();
private LocationBackend backend;
private boolean updateWaiting;
private Location lastLocation;
private boolean bound;
import static org.microg.nlp.api.NlpApiConstants.LOCATION_EXTRA_BACKEND_COMPONENT;
import static org.microg.nlp.api.NlpApiConstants.LOCATION_EXTRA_BACKEND_PROVIDER;
public BackendHelper(Context context, LocationProvider provider, Intent serviceIntent) {
this.context = context;
this.provider = provider;
this.serviceIntent = serviceIntent;
}
class BackendHelper extends AbstractBackendHelper {
private static final String TAG = BackendHelper.class.getName();
private final BackendFuser backendFuser;
private final Callback callback = new Callback();
private LocationBackend backend;
private boolean updateWaiting;
private Location lastLocation;
@Override
public boolean bind() {
if (!bound) {
try {
context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
} catch (Exception e) {
Log.w(TAG, e);
return false;
}
}
return true;
}
public BackendHelper(Context context, BackendFuser backendFuser, Intent serviceIntent) {
super(TAG, context, serviceIntent);
this.backendFuser = backendFuser;
}
@Override
public void unbind() {
if (bound) {
if (backend != null) {
try {
backend.close();
} catch (Exception e) {
Log.w(TAG, e);
}
}
try {
context.unbindService(connection);
} catch (Exception e) {
Log.w(TAG, e);
}
bound = false;
}
}
public Location getLastLocation() {
return lastLocation;
}
@Override
public Location getLastLocation() {
return lastLocation;
}
public Location update() {
if (backend == null) {
Log.d(TAG, "Not (yet) bound.");
updateWaiting = true;
} else {
updateWaiting = false;
try {
setLastLocation(backend.update());
backendFuser.reportLocation();
} catch (Exception e) {
Log.w(TAG, e);
unbind();
}
}
return lastLocation;
}
@Override
public Location update() {
if (backend == null) {
Log.d(TAG, "Not (yet) bound.");
updateWaiting = true;
} else {
updateWaiting = false;
try {
setLastLocation(backend.update());
provider.reportLocation(lastLocation);
} catch (Exception e) {
Log.w(TAG, e);
unbind();
}
}
return lastLocation;
}
private Location setLastLocation(Location location) {
if (location == null) {
return lastLocation;
}
if (!location.hasAccuracy()) {
return lastLocation;
}
if (location.getExtras() == null) {
location.setExtras(new Bundle());
}
location.getExtras().putString(LOCATION_EXTRA_BACKEND_PROVIDER, location.getProvider());
location.getExtras().putString(LOCATION_EXTRA_BACKEND_COMPONENT,
serviceIntent.getComponent().flattenToShortString());
if (!location.hasAccuracy()) {
location.setAccuracy(50000);
}
if (location.getTime() <= 0) {
location.setTime(System.currentTimeMillis());
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (location.getElapsedRealtimeNanos() <= 0) {
location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
}
location.getExtras()
.putParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION, new Location(location));
lastLocation = location;
return lastLocation;
}
private Location setLastLocation(Location location) {
if (location == null) {
return lastLocation;
}
if (!location.hasAccuracy()) {
return lastLocation;
}
if (location.getExtras() == null) {
location.setExtras(new Bundle());
}
location.getExtras().putString("SERVICE_BACKEND_PROVIDER", location.getProvider());
location.getExtras().putString("SERVICE_BACKEND_COMPONENT", serviceIntent.getComponent().flattenToShortString());
location.setProvider("network");
if (!location.hasAccuracy()) {
location.setAccuracy(50000);
}
if (location.getTime() <= 0) {
location.setTime(System.currentTimeMillis());
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (location.getElapsedRealtimeNanos() <= 0) {
location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
}
location.getExtras().putParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION, new Location(location));
lastLocation = location;
return lastLocation;
}
@Override
public void close() throws RemoteException {
backend.close();
}
private class Connection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bound = true;
backend = LocationBackend.Stub.asInterface(service);
if (backend != null) {
try {
backend.open(callback);
if (updateWaiting) {
update();
}
} catch (Exception e) {
Log.w(TAG, e);
unbind();
}
}
}
@Override
public boolean hasBackend() {
return backend != null;
}
@Override
public void onServiceDisconnected(ComponentName name) {
backend = null;
bound = false;
}
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bound = true;
backend = LocationBackend.Stub.asInterface(service);
if (backend != null) {
try {
backend.open(callback);
if (updateWaiting) {
update();
}
} catch (Exception e) {
Log.w(TAG, e);
unbind();
}
}
}
private class Callback extends LocationCallback.Stub {
@Override
public void report(Location location) throws RemoteException {
provider.reportLocation(setLastLocation(location));
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
backend = null;
bound = false;
}
private class Callback extends LocationCallback.Stub {
@Override
public void report(Location location) throws RemoteException {
setLastLocation(location);
backendFuser.reportLocation();
}
}
}

View file

@ -3,10 +3,12 @@ package org.microg.nlp.location;
import android.location.Location;
import org.microg.nlp.Provider;
public interface LocationProvider extends Provider {
void onEnable();
interface LocationProvider extends Provider {
void onEnable();
void onDisable();
void onDisable();
void reportLocation(Location location);
void reportLocation(Location location);
void forceLocation(Location location);
}

View file

@ -2,6 +2,7 @@ package org.microg.nlp.location;
import android.content.Context;
import android.location.Criteria;
import android.location.Location;
import android.os.Bundle;
import android.os.WorkSource;
import android.util.Log;
@ -12,73 +13,79 @@ import com.android.location.provider.ProviderRequestUnbundled;
import static android.location.LocationProvider.AVAILABLE;
public class LocationProviderV2 extends LocationProviderBase implements LocationProvider {
private static final String TAG = LocationProviderV2.class.getName();
private static final ProviderPropertiesUnbundled props = ProviderPropertiesUnbundled.create(
false, // requiresNetwork
false, // requiresSatellite
false, // requiresCell
false, // hasMonetaryCost
true, // supportsAltitude
true, // supportsSpeed
true, // supportsBearing
Criteria.POWER_LOW, // powerRequirement
Criteria.ACCURACY_COARSE); // accuracy
private ThreadHelper helper;
class LocationProviderV2 extends LocationProviderBase implements LocationProvider {
private static final String TAG = LocationProviderV2.class.getName();
private static final ProviderPropertiesUnbundled props = ProviderPropertiesUnbundled.create(
false, // requiresNetwork
false, // requiresSatellite
false, // requiresCell
false, // hasMonetaryCost
true, // supportsAltitude
true, // supportsSpeed
true, // supportsBearing
Criteria.POWER_LOW, // powerRequirement
Criteria.ACCURACY_COARSE); // accuracy
private final ThreadHelper helper;
public LocationProviderV2(Context context) {
super(TAG, props);
this.helper = new ThreadHelper(context, this);
}
public LocationProviderV2(Context context) {
super(TAG, props);
this.helper = new ThreadHelper(context, this);
}
@Override
public void onDisable() {
}
@Override
public void onDisable() {
}
@Override
public void reload() {
helper.reload();
}
@Override
public void forceLocation(Location location) {
helper.forceLocation(location);
}
@Override
public void onEnable() {
}
@Override
public void reload() {
helper.reload();
}
@Override
public int onGetStatus(Bundle extras) {
return AVAILABLE;
}
@Override
public void onEnable() {
}
@Override
public long onGetStatusUpdateTime() {
return 0;
}
@Override
public int onGetStatus(Bundle extras) {
return AVAILABLE;
}
@Override
public void onSetRequest(ProviderRequestUnbundled requests, WorkSource source) {
Log.v(TAG, "onSetRequest: " + requests + " by " + source);
@Override
public long onGetStatusUpdateTime() {
return 0;
}
long autoTime = Long.MAX_VALUE;
boolean autoUpdate = false;
for (LocationRequestUnbundled request : requests.getLocationRequests()) {
Log.v(TAG, "onSetRequest: request: " + request);
if (autoTime > request.getInterval()) {
autoTime = request.getInterval();
}
autoUpdate = true;
}
@Override
public void onSetRequest(ProviderRequestUnbundled requests, WorkSource source) {
Log.v(TAG, "onSetRequest: " + requests + " by " + source);
if (autoTime < 1500) {
// Limit to 1.5s
autoTime = 1500;
}
Log.v(TAG, "using autoUpdate=" + autoUpdate + " autoTime=" + autoTime);
long autoTime = Long.MAX_VALUE;
boolean autoUpdate = false;
for (LocationRequestUnbundled request : requests.getLocationRequests()) {
Log.v(TAG, "onSetRequest: request: " + request);
if (autoTime > request.getInterval()) {
autoTime = request.getInterval();
}
autoUpdate = true;
}
if (autoTime < 1500) {
// Limit to 1.5s
autoTime = 1500;
}
Log.v(TAG, "using autoUpdate=" + autoUpdate + " autoTime=" + autoTime);
if (autoUpdate) {
helper.setTime(autoTime);
helper.enable();
} else {
helper.disable();
}
}
if (autoUpdate) {
helper.setTime(autoTime);
helper.enable();
} else {
helper.disable();
}
}
}

View file

@ -1,66 +1,61 @@
package org.microg.nlp.location;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import org.microg.nlp.Provider;
import org.microg.nlp.ProviderService;
public abstract class LocationService extends ProviderService {
public static final String ACTION_RELOAD_SETTINGS = "org.microg.nlp.RELOAD_SETTINGS";
import static org.microg.nlp.api.NlpApiConstants.*;
public static void reloadLocationService(Context context) {
Intent intent = new Intent(LocationService.ACTION_RELOAD_SETTINGS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
intent.setClass(context, LocationServiceV2.class);
} else {
// TODO
}
context.startService(intent);
}
public abstract class LocationService extends ProviderService<LocationProvider> {
public static void reloadLocationService(Context context) {
Intent intent = new Intent(ACTION_RELOAD_SETTINGS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
intent.setClass(context, LocationServiceV2.class);
} else {
// TODO
}
context.startService(intent);
}
/**
* Creates an LocationService. Invoked by your subclass's constructor.
*
* @param tag Used for debugging.
*/
public LocationService(String tag) {
super(tag);
}
/**
* Creates an LocationService. Invoked by your subclass's constructor.
*
* @param tag Used for debugging.
*/
public LocationService(String tag) {
super(tag);
}
@Override
protected void onHandleIntent(Intent intent) {
/*
* There is an undocumented way to send locations via intent in Google's LocationService.
* This intent based location is not secure, that's why it's not active here,
* but maybe we will add it in the future, could be a nice debugging feature :)
*/
@Override
protected void onHandleIntent(Intent intent) {
if (ACTION_FORCE_LOCATION.equals(intent.getAction())) {
if (checkCallingPermission(PERMISSION_FORCE_LOCATION) ==
PackageManager.PERMISSION_GRANTED) {
LocationProvider provider = getCurrentProvider();
if (provider != null && intent.hasExtra(INTENT_EXTRA_LOCATION)) {
provider.forceLocation(
(Location) intent.getParcelableExtra(INTENT_EXTRA_LOCATION));
}
}
}
/*
Location location = intent.getParcelableExtra("location");
if (nlprovider != null && location != null) {
nlprovider.reportLocation(location);
}
*/
if (ACTION_RELOAD_SETTINGS.equals(intent.getAction())) {
LocationProvider provider = getCurrentProvider();
if (provider != null) {
provider.reload();
}
}
}
if (ACTION_RELOAD_SETTINGS.equals(intent.getAction())) {
Provider provider = getCurrentProvider();
if (provider != null) {
provider.reload();
}
}
}
@Override
public boolean onUnbind(Intent intent) {
Provider provider = getCurrentProvider();
if (provider instanceof LocationProvider) {
((LocationProvider) provider).onDisable();
}
return super.onUnbind(intent);
}
@Override
public boolean onUnbind(Intent intent) {
LocationProvider provider = getCurrentProvider();
if (provider != null) {
provider.onDisable();
}
return super.onUnbind(intent);
}
}

View file

@ -1,14 +1,14 @@
package org.microg.nlp.location;
public class LocationServiceV2 extends LocationService {
private static final String TAG = LocationServiceV2.class.getName();
private static final String TAG = LocationServiceV2.class.getName();
public LocationServiceV2() {
super(TAG);
}
public LocationServiceV2() {
super(TAG);
}
@Override
protected LocationProvider createProvider() {
return new LocationProviderV2(this);
}
@Override
protected LocationProvider createProvider() {
return new LocationProviderV2(this);
}
}

View file

@ -1,78 +1,77 @@
package org.microg.nlp.location;
import android.content.Context;
import android.location.Location;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadHelper implements Runnable {
private final Context context;
private final LocationProvider locationProvider;
private BackendHandler backendHandler;
private ScheduledThreadPoolExecutor executor;
private long time = 5000; // Initialize with 5s
private boolean enabled;
class ThreadHelper implements Runnable {
private final Context context;
private final LocationProvider locationProvider;
private BackendFuser backendFuser;
private ScheduledThreadPoolExecutor executor;
private long time = 5000; // Initialize with 5s
private boolean enabled;
public ThreadHelper(Context context, LocationProvider locationProvider) {
this.context = context;
this.locationProvider = locationProvider;
updateBackendHandler();
}
public ThreadHelper(Context context, LocationProvider locationProvider) {
this.context = context;
this.locationProvider = locationProvider;
updateBackendHandler();
}
private void updateBackendHandler() {
List<BackendFuser.BackendInfo> backendList = new ArrayList<BackendFuser.BackendInfo>();
String backends = context.getSharedPreferences("config", Context.MODE_PRIVATE).getString("location_backends", "");
for (String backend : backends.split("\\|")) {
String[] parts = backend.split("/");
if (parts.length == 2) {
backendList.add(new BackendFuser.BackendInfo(parts[0], parts[1]));
}
}
backendHandler = new BackendFuser(context, backendList, locationProvider);
}
private void updateBackendHandler() {
BackendFuser old = backendFuser;
backendFuser = new BackendFuser(context, locationProvider);
if (old != null) {
backendFuser.forceLocation(old.getForcedLocation());
}
}
public void reload() {
disable();
updateBackendHandler();
enable();
}
public void forceLocation(Location location) {
backendFuser.forceLocation(location);
}
public void disable() {
if (executor != null) {
executor.shutdownNow();
executor = null;
}
if (enabled) {
backendHandler.unbind();
enabled = false;
}
}
public void reload() {
disable();
updateBackendHandler();
enable();
}
public void setTime(long time) {
this.time = time;
}
public void disable() {
if (executor != null) {
executor.shutdownNow();
executor = null;
}
if (enabled) {
backendFuser.unbind();
enabled = false;
}
}
public void reset() {
if (executor != null) {
executor.shutdownNow();
executor = null;
}
executor = new ScheduledThreadPoolExecutor(1);
executor.scheduleAtFixedRate(this, 0, time, TimeUnit.MILLISECONDS);
}
public void setTime(long time) {
this.time = time;
}
public void enable() {
if (!enabled) {
backendHandler.bind();
enabled = true;
}
reset();
}
public void reset() {
if (executor != null) {
executor.shutdownNow();
executor = null;
}
executor = new ScheduledThreadPoolExecutor(1);
executor.scheduleAtFixedRate(this, 0, time, TimeUnit.MILLISECONDS);
}
@Override
public void run() {
backendHandler.update();
}
public void enable() {
if (!enabled) {
backendFuser.bind();
enabled = true;
}
reset();
}
@Override
public void run() {
backendFuser.update();
}
}

File diff suppressed because it is too large Load diff

View file

@ -18,8 +18,8 @@ import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import org.microg.nlp.Preferences;
import org.microg.nlp.R;
import org.microg.nlp.api.NlpApiConstants;
import org.microg.nlp.location.LocationService;
import java.util.ArrayList;
@ -27,228 +27,235 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.microg.nlp.api.NlpApiConstants.ACTION_LOCATION_BACKEND;
import static org.microg.nlp.api.NlpApiConstants.METADATA_BACKEND_SETTINGS_ACTIVITY;
public class LocationBackendConfig extends Activity {
private static final String TAG = LocationBackendConfig.class.getName();
private static final String TAG = LocationBackendConfig.class.getName();
private List<ServiceInfo> activeBackends;
private Map<ServiceInfo, KnownBackend> knownBackends;
private List<ServiceInfo> unusedBackends;
private Adapter adapter;
private DynamicListView listView;
private View addButton;
private PopupMenu popUp;
private List<ServiceInfo> activeBackends;
private Map<ServiceInfo, KnownBackend> knownBackends;
private List<ServiceInfo> unusedBackends;
private Adapter adapter;
private DynamicListView listView;
private View addButton;
private PopupMenu popUp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pluginselection);
listView = (DynamicListView) findViewById(android.R.id.list);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> view, View view2, int i, long l) {
Log.d(TAG, "onItemClick: " + l);
}
});
addButton = findViewById(android.R.id.button1);
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showAddPluginPopup(view);
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pluginselection);
listView = (DynamicListView) findViewById(android.R.id.list);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> view, View view2, int i, long l) {
Log.d(TAG, "onItemClick: " + l);
}
});
addButton = findViewById(android.R.id.button1);
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showAddPluginPopup(view);
}
});
updateBackends();
}
updateBackends();
}
private void updateBackends() {
activeBackends = new ArrayList<ServiceInfo>();
knownBackends = new HashMap<ServiceInfo, KnownBackend>();
unusedBackends = new ArrayList<ServiceInfo>();
Intent intent = new Intent(NlpApiConstants.ACTION_LOCATION_BACKEND);
List<ResolveInfo> resolveInfos = getPackageManager().queryIntentServices(intent, PackageManager.GET_META_DATA);
for (ResolveInfo info : resolveInfos) {
ServiceInfo serviceInfo = info.serviceInfo;
String simpleName = String.valueOf(serviceInfo.loadLabel(getPackageManager()));
Drawable icon = serviceInfo.loadIcon(getPackageManager());
knownBackends.put(serviceInfo, new KnownBackend(serviceInfo, simpleName, icon));
}
String activeBackendString = getSharedPreferences("config", MODE_PRIVATE).getString("location_backends", "");
for (String backend : activeBackendString.split("\\|")) {
String[] parts = backend.split("/");
if (parts.length == 2) {
for (ServiceInfo serviceInfo : knownBackends.keySet()) {
if (serviceInfo.packageName.equals(parts[0]) && serviceInfo.name.equals(parts[1])) {
activeBackends.add(serviceInfo);
}
}
}
}
updateAddButton();
resetAdapter();
}
private void updateBackends() {
activeBackends = new ArrayList<ServiceInfo>();
knownBackends = new HashMap<ServiceInfo, KnownBackend>();
unusedBackends = new ArrayList<ServiceInfo>();
Intent intent = new Intent(ACTION_LOCATION_BACKEND);
List<ResolveInfo> resolveInfos = getPackageManager()
.queryIntentServices(intent, PackageManager.GET_META_DATA);
for (ResolveInfo info : resolveInfos) {
ServiceInfo serviceInfo = info.serviceInfo;
String simpleName = String.valueOf(serviceInfo.loadLabel(getPackageManager()));
Drawable icon = serviceInfo.loadIcon(getPackageManager());
knownBackends.put(serviceInfo, new KnownBackend(serviceInfo, simpleName, icon));
}
for (String backend : new Preferences(this).getLocationBackends()) {
String[] parts = backend.split("/");
if (parts.length == 2) {
for (ServiceInfo serviceInfo : knownBackends.keySet()) {
if (serviceInfo.packageName.equals(parts[0]) &&
serviceInfo.name.equals(parts[1])) {
activeBackends.add(serviceInfo);
}
}
}
}
updateAddButton();
resetAdapter();
}
private void updateAddButton() {
if (activeBackends.size() == knownBackends.size()) {
if (knownBackends.isEmpty()) {
// No backend installed
// TODO: notify user about that
}
addButton.setVisibility(View.GONE);
} else {
addButton.setVisibility(View.VISIBLE);
}
}
private void updateAddButton() {
if (activeBackends.size() == knownBackends.size()) {
if (knownBackends.isEmpty()) {
// No backend installed
// TODO: notify user about that
}
addButton.setVisibility(View.GONE);
} else {
addButton.setVisibility(View.VISIBLE);
}
}
private void resetAdapter() {
List<ServiceInfo> backends = activeBackends;
adapter = new Adapter(backends);
listView.setList(backends);
listView.setAdapter(adapter);
}
private void resetAdapter() {
List<ServiceInfo> backends = activeBackends;
adapter = new Adapter(backends);
listView.setList(backends);
listView.setAdapter(adapter);
}
private void updateUnusedBackends() {
unusedBackends.clear();
for (KnownBackend backend : knownBackends.values()) {
if (!activeBackends.contains(backend.serviceInfo)) {
unusedBackends.add(backend.serviceInfo);
}
}
}
private void updateUnusedBackends() {
unusedBackends.clear();
for (KnownBackend backend : knownBackends.values()) {
if (!activeBackends.contains(backend.serviceInfo)) {
unusedBackends.add(backend.serviceInfo);
}
}
}
private void enableBackend(ServiceInfo serviceInfo) {
activeBackends.add(serviceInfo);
onBackendsChanged();
}
private void enableBackend(ServiceInfo serviceInfo) {
activeBackends.add(serviceInfo);
onBackendsChanged();
}
private void disabledBackend(ServiceInfo serviceInfo) {
activeBackends.remove(serviceInfo);
onBackendsChanged();
}
private void disabledBackend(ServiceInfo serviceInfo) {
activeBackends.remove(serviceInfo);
onBackendsChanged();
}
private void onBackendsChanged() {
updateAddButton();
resetAdapter();
getSharedPreferences("config", MODE_PRIVATE).edit().putString("location_backends", backendString(activeBackends)).commit();
LocationService.reloadLocationService(this);
}
private void onBackendsChanged() {
updateAddButton();
resetAdapter();
new Preferences(this).setLocationBackends(backendString(activeBackends));
LocationService.reloadLocationService(this);
}
private String backendString(List<ServiceInfo> backends) {
StringBuilder sb = new StringBuilder();
for (ServiceInfo backend : backends) {
if (sb.length() != 0) {
sb.append("|");
}
sb.append(backend.packageName).append("/").append(backend.name);
}
return sb.toString();
}
private String backendString(List<ServiceInfo> backends) {
StringBuilder sb = new StringBuilder();
for (ServiceInfo backend : backends) {
if (sb.length() != 0) {
sb.append("|");
}
sb.append(backend.packageName).append("/").append(backend.name);
}
return sb.toString();
}
private void showAddPluginPopup(View anchorView) {
updateUnusedBackends();
private void showAddPluginPopup(View anchorView) {
updateUnusedBackends();
if (popUp != null) {
popUp.dismiss();
}
popUp = new PopupMenu(this, anchorView);
if (popUp != null) {
popUp.dismiss();
}
popUp = new PopupMenu(this, anchorView);
for (int i = 0; i < unusedBackends.size(); i++) {
KnownBackend backend = knownBackends.get(unusedBackends.get(i));
String label = backend.simpleName;
if (TextUtils.isEmpty(label)) {
label = backend.serviceInfo.name;
}
popUp.getMenu().add(Menu.NONE, i, Menu.NONE, label);
}
popUp.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
popUp.dismiss();
popUp = null;
for (int i = 0; i < unusedBackends.size(); i++) {
KnownBackend backend = knownBackends.get(unusedBackends.get(i));
String label = backend.simpleName;
if (TextUtils.isEmpty(label)) {
label = backend.serviceInfo.name;
}
popUp.getMenu().add(Menu.NONE, i, Menu.NONE, label);
}
popUp.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
popUp.dismiss();
popUp = null;
enableBackend(unusedBackends.get(menuItem.getItemId()));
return true;
}
});
popUp.show();
}
enableBackend(unusedBackends.get(menuItem.getItemId()));
return true;
}
});
popUp.show();
}
private Intent createSettingsIntent(ComponentInfo componentInfo) {
Intent settingsIntent = new Intent(Intent.ACTION_VIEW);
settingsIntent.setPackage(componentInfo.packageName);
settingsIntent.setClassName(componentInfo.packageName,
componentInfo.metaData.getString(NlpApiConstants.METADATA_BACKEND_SETTINGS_ACTIVITY));
return settingsIntent;
}
private Intent createSettingsIntent(ComponentInfo componentInfo) {
Intent settingsIntent = new Intent(Intent.ACTION_VIEW);
settingsIntent.setPackage(componentInfo.packageName);
settingsIntent.setClassName(componentInfo.packageName,
componentInfo.metaData.getString(METADATA_BACKEND_SETTINGS_ACTIVITY));
return settingsIntent;
}
private void showSettingsPopup(View anchorView, final KnownBackend backend) {
if (popUp != null) {
popUp.dismiss();
}
popUp = new PopupMenu(this, anchorView);
popUp.getMenu().add(Menu.NONE, 0, Menu.NONE, "Remove"); // TODO label
if (backend.serviceInfo.metaData != null &&
backend.serviceInfo.metaData.getString(NlpApiConstants.METADATA_BACKEND_SETTINGS_ACTIVITY) != null) {
if (getPackageManager().resolveActivity(createSettingsIntent(backend.serviceInfo), 0) != null) {
popUp.getMenu().add(Menu.NONE, 1, Menu.NONE, "Settings"); // TODO label
}
}
popUp.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
popUp.dismiss();
popUp = null;
private void showSettingsPopup(View anchorView, final KnownBackend backend) {
if (popUp != null) {
popUp.dismiss();
}
popUp = new PopupMenu(this, anchorView);
popUp.getMenu().add(Menu.NONE, 0, Menu.NONE, "Remove"); // TODO label
if (backend.serviceInfo.metaData != null &&
backend.serviceInfo.metaData.getString(METADATA_BACKEND_SETTINGS_ACTIVITY) !=
null) {
if (getPackageManager().resolveActivity(createSettingsIntent(backend.serviceInfo), 0) !=
null) {
popUp.getMenu().add(Menu.NONE, 1, Menu.NONE, "Settings"); // TODO label
}
}
popUp.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
popUp.dismiss();
popUp = null;
if (item.getItemId() == 0) {
disabledBackend(backend.serviceInfo);
} else if (item.getItemId() == 1) {
startActivity(createSettingsIntent(backend.serviceInfo));
}
return true;
}
});
popUp.show();
}
if (item.getItemId() == 0) {
disabledBackend(backend.serviceInfo);
} else if (item.getItemId() == 1) {
startActivity(createSettingsIntent(backend.serviceInfo));
}
return true;
}
});
popUp.show();
}
private class Adapter extends DynamicListView.StableArrayAdapter {
public Adapter(List<ServiceInfo> backends) {
super(LocationBackendConfig.this, R.layout.backend_list_entry, android.R.id.text2, backends);
}
private class Adapter extends DynamicListView.StableArrayAdapter {
public Adapter(List<ServiceInfo> backends) {
super(LocationBackendConfig.this, R.layout.backend_list_entry, android.R.id.text2,
backends);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = super.getView(position, convertView, parent);
final KnownBackend backend = knownBackends.get(getItem(position));
ImageView icon = (ImageView) v.findViewById(android.R.id.icon);
icon.setImageDrawable(backend.icon);
TextView title = (TextView) v.findViewById(android.R.id.text1);
title.setText(backend.simpleName);
TextView subtitle = (TextView) v.findViewById(android.R.id.text2);
subtitle.setText(backend.serviceInfo.name);
View overflow = v.findViewById(android.R.id.button1);
overflow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showSettingsPopup(view, backend);
}
});
return v;
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = super.getView(position, convertView, parent);
final KnownBackend backend = knownBackends.get(getItem(position));
ImageView icon = (ImageView) v.findViewById(android.R.id.icon);
icon.setImageDrawable(backend.icon);
TextView title = (TextView) v.findViewById(android.R.id.text1);
title.setText(backend.simpleName);
TextView subtitle = (TextView) v.findViewById(android.R.id.text2);
subtitle.setText(backend.serviceInfo.name);
View overflow = v.findViewById(android.R.id.button1);
overflow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showSettingsPopup(view, backend);
}
});
return v;
}
}
private class KnownBackend {
private ServiceInfo serviceInfo;
private String simpleName;
private Drawable icon;
private class KnownBackend {
private ServiceInfo serviceInfo;
private String simpleName;
private Drawable icon;
private KnownBackend(ServiceInfo serviceInfo, String simpleName, Drawable icon) {
this.serviceInfo = serviceInfo;
this.simpleName = simpleName;
this.icon = icon;
}
private KnownBackend(ServiceInfo serviceInfo, String simpleName, Drawable icon) {
this.serviceInfo = serviceInfo;
this.simpleName = simpleName;
this.icon = icon;
}
@Override
public String toString() {
return simpleName;
}
}
}
@Override
public String toString() {
return simpleName;
}
}
}