Initial commit

This commit is contained in:
mar-v-in 2014-03-05 21:31:46 +01:00
commit 855ea472d3
31 changed files with 813 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.iml
gen
bin

34
Android.mk Normal file
View File

@ -0,0 +1,34 @@
# Copyright (c) 2014 μg Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
LOCAL_PATH:= $(call my-dir)
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...
LOCAL_JAVA_LIBRARIES := framework com.android.location.provider
LOCAL_STATIC_JAVA_LIBRARIES := UnifiedNlpApi
LOCAL_PACKAGE_NAME := UnifiedNlp
LOCAL_SDK_VERSION := current
LOCAL_PRIVILEGED_MODULE := true
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))

74
AndroidManifest.xml Normal file
View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.microg.nlp">
<uses-sdk android:minSdkVersion="9" />
<application
android:icon="@drawable/nlp_app_icon"
android:label="@string/nlp_app_name">
<uses-library
android:name="com.android.location.provider"
android:required="false" />
<!-- 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>
<meta-data
android:name="serviceVersion"
android:value="1" />
<meta-data
android:name="version"
android:value="1" />
</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>
<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>
<meta-data
android:name="serviceVersion"
android:value="2" />
<meta-data
android:name="serviceIsMultiuser"
android:value="false" />
</service>
<activity
android:name="org.microg.nlp.ui.LocationBackendConfig"
android:theme="@android:style/Theme.DeviceDefault.Light">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

6
README.md Normal file
View File

@ -0,0 +1,6 @@
The next generation NetworkLocationProvider, based on plugins
`api` contains files needed to build your custom plugin.
`sample` contains a sample plugin.
To be build with Android Build System using `make UnifiedNlp`

9
api/Android.mk Normal file
View File

@ -0,0 +1,9 @@
LOCAL_PATH := $(call my-dir)
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/LocationCallback.aidl
include $(BUILD_STATIC_JAVA_LIBRARY)

4
api/AndroidManifest.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="org.microg.nlp.api">
</manifest>

View File

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

View File

@ -0,0 +1,71 @@
package org.microg.nlp.api;
import android.app.Service;
import android.content.Intent;
import android.location.Location;
import android.os.IBinder;
import android.os.RemoteException;
public abstract class LocationBackendService extends Service {
private Backend backend = new Backend();
private LocationCallback callback;
private Location waiting;
/**
* This method is 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 abstract Location 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 (RemoteException e) {
waiting = location;
}
} else {
waiting = location;
}
}
@Override
public IBinder onBind(Intent intent) {
return backend;
}
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;
}
}
@Override
public Location update() throws RemoteException {
return LocationBackendService.this.update();
}
@Override
public void close() throws RemoteException {
callback = null;
}
}
}

View File

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

View File

@ -0,0 +1,5 @@
package org.microg.nlp.api;
public class NlpApiConstants {
public static final String ACTION_LOCATION_BACKEND = "org.microg.nlp.LOCATION_BACKEND";
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

4
res/values/strings.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nlp_app_name">NLP Controller</string>
</resources>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.microg.nlp.api.sample">
<uses-sdk android:minSdkVersion="19" />
<application
android:icon="@drawable/icon"
android:label="@string/app_name">
<service
android:name=".SampleBackendService"
android:exported="true">
<intent-filter>
<action android:name="org.microg.nlp.LOCATION_BACKEND" />
</intent-filter>
</service>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">NetworkLocationV2-SamplePlugin</string>
</resources>

View File

@ -0,0 +1,19 @@
package org.microg.nlp.api.sample;
import android.location.Location;
import android.util.Log;
import org.microg.nlp.api.LocationBackendService;
public class SampleBackendService extends LocationBackendService {
private static final String TAG = SampleBackendService.class.getName();
@Override
protected Location update() {
Location location = new Location("sample");
location.setLatitude(42);
location.setLongitude(42);
location.setAccuracy(42);
Log.d(TAG, "I was asked for location and I answer: " + location);
return location;
}
}

View File

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

View File

@ -0,0 +1,53 @@
package org.microg.nlp;
import android.app.IntentService;
import android.content.Intent;
import android.os.IBinder;
public abstract class ProviderService extends IntentService {
private Provider provider;
/**
* Creates an ProviderService. Invoked by your subclass's constructor.
*
* @param tag Used for debugging.
*/
public ProviderService(String tag) {
super(tag);
}
@Override
public void onCreate() {
provider = createProvider();
}
@Override
public IBinder onBind(Intent intent) {
return provider.getBinder();
}
@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();
@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;
}
}

View File

@ -0,0 +1,6 @@
package org.microg.nlp.geocode;
import org.microg.nlp.Provider;
public interface GeocodeProvider extends Provider {
}

View File

@ -0,0 +1,22 @@
package org.microg.nlp.geocode;
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;
}
@Override
public String onGetFromLocationName(String locationName, double lowerLeftLatitude, double lowerLeftLongitude,
double upperRightLatitude, double upperRightLongitude, int maxResults,
GeocoderParams params, List<Address> addresses) {
return null;
}
}

View File

@ -0,0 +1,17 @@
package org.microg.nlp.geocode;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
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);
}
}

View File

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

View File

@ -0,0 +1,132 @@
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.api.LocationBackend;
import org.microg.nlp.api.LocationCallback;
public class BackendHelper {
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;
public BackendHelper(Context context, LocationProvider provider, Intent serviceIntent) {
this.context = context;
this.provider = provider;
this.serviceIntent = serviceIntent;
}
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 void unbind() {
if (bound) {
try {
backend.close();
context.unbindService(connection);
} catch (Exception e) {
Log.w(TAG, e);
}
}
}
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());
provider.reportLocation(lastLocation);
} catch (RemoteException e) {
Log.w(TAG, e);
unbind();
}
}
return lastLocation;
}
private Location setLastLocation(Location location) {
if (location == null) {
return lastLocation;
}
if (location.getExtras() == null) {
location.setExtras(new Bundle());
}
location.getExtras().putString("SERVICE_BACKEND_PROVIDER", location.getProvider());
location.getExtras().putString("SERVICE_BACKEND_PACKAGE", serviceIntent.getPackage());
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;
Log.v(TAG, "location=" + lastLocation);
return lastLocation;
}
private class Connection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bound = true;
backend = LocationBackend.Stub.asInterface(service);
try {
backend.open(callback);
if (updateWaiting) {
update();
}
} catch (RemoteException e) {
Log.w(TAG, e);
unbind();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
backend = null;
bound = false;
}
}
private class Callback extends LocationCallback.Stub {
@Override
public void report(Location location) throws RemoteException {
provider.reportLocation(setLastLocation(location));
}
}
}

View File

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

View File

@ -0,0 +1,79 @@
package org.microg.nlp.location;
import android.content.Context;
import android.location.Criteria;
import android.os.Bundle;
import android.os.WorkSource;
import android.util.Log;
import com.android.location.provider.LocationProviderBase;
import com.android.location.provider.LocationRequestUnbundled;
import com.android.location.provider.ProviderPropertiesUnbundled;
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 final ThreadHelper handler;
public LocationProviderV2(Context context) {
super(TAG, props);
handler = new ThreadHelper(context, this);
}
@Override
public void onDisable() {
}
@Override
public void onEnable() {
}
@Override
public int onGetStatus(Bundle extras) {
return AVAILABLE;
}
@Override
public long onGetStatusUpdateTime() {
return 0;
}
@Override
public void onSetRequest(ProviderRequestUnbundled requests, WorkSource source) {
Log.v(TAG, "onSetRequest: " + requests + " by " + source);
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) {
handler.setTime(autoTime);
handler.enable();
} else {
handler.disable();
}
}
}

View File

@ -0,0 +1,41 @@
package org.microg.nlp.location;
import android.content.Intent;
import org.microg.nlp.Provider;
import org.microg.nlp.ProviderService;
public abstract class LocationService extends ProviderService {
/**
* 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 :)
*/
/*
Location location = intent.getParcelableExtra("location");
if (nlprovider != null && location != null) {
nlprovider.reportLocation(location);
}
*/
}
@Override
public boolean onUnbind(Intent intent) {
Provider provider = getCurrentProvider();
if (provider instanceof LocationProvider) {
((LocationProvider) provider).onDisable();
}
return false;
}
}

View File

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

View File

@ -0,0 +1,56 @@
package org.microg.nlp.location;
import android.content.Context;
import android.content.Intent;
import org.microg.nlp.api.NlpApiConstants;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadHelper implements Runnable {
private BackendHelper api;
private ScheduledThreadPoolExecutor executor;
private long time = 5000; // Initialize with 5s
private boolean enabled;
public ThreadHelper(Context context, LocationProvider provider) {
api = new BackendHelper(context, provider, new Intent(NlpApiConstants.ACTION_LOCATION_BACKEND));
}
public void disable() {
if (executor != null) {
executor.shutdownNow();
executor = null;
}
if (enabled) {
api.unbind();
enabled = false;
}
}
public void setTime(long time) {
this.time = time;
}
public void reset() {
if (executor != null) {
executor.shutdownNow();
executor = null;
}
executor = new ScheduledThreadPoolExecutor(1);
executor.scheduleAtFixedRate(this, 0, time, TimeUnit.MILLISECONDS);
}
public void enable() {
if (!enabled) {
api.bind();
enabled = true;
}
reset();
}
@Override
public void run() {
api.update();
}
}

View File

@ -0,0 +1,91 @@
package org.microg.nlp.ui;
import android.app.ListActivity;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import org.microg.nlp.api.NlpApiConstants;
import java.util.ArrayList;
import java.util.List;
public class LocationBackendConfig extends ListActivity {
private List<String> activeBackends;
private Adapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activeBackends = new ArrayList<String>();
List<KnownBackend> backends = new ArrayList<KnownBackend>();
Intent intent = new Intent(NlpApiConstants.ACTION_LOCATION_BACKEND);
List<ResolveInfo> resolveInfos = getPackageManager().queryIntentServices(intent, 0);
for (ResolveInfo info : resolveInfos) {
String packageName = info.serviceInfo.packageName;
String simpleName = String.valueOf(info.serviceInfo.loadLabel(getPackageManager()));
Drawable icon = info.serviceInfo.loadIcon(getPackageManager());
backends.add(new KnownBackend(packageName, simpleName, icon));
}
adapter = new Adapter(backends);
setListAdapter(adapter);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
KnownBackend backend = adapter.getItem(position);
if (activeBackends.contains(backend.packageName)) {
activeBackends.remove(backend.packageName);
} else {
activeBackends.add(backend.packageName);
}
adapter.notifyDataSetChanged();
}
private class Adapter extends ArrayAdapter<KnownBackend> {
public Adapter(List<KnownBackend> backends) {
super(LocationBackendConfig.this, android.R.layout.select_dialog_multichoice, android.R.id.text1, backends);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// User super class to create the View
View v = super.getView(position, convertView, parent);
CheckedTextView tv = (CheckedTextView) v.findViewById(android.R.id.text1);
// Put the image on the TextView
tv.setCompoundDrawablesWithIntrinsicBounds(getItem(position).icon, null,
null, null);
// Add margin between image and text (support various screen densities)
int dp10 = (int) (10 * getContext().getResources().getDisplayMetrics().density + 0.5f);
tv.setCompoundDrawablePadding(dp10);
tv.setChecked(activeBackends.contains(getItem(position).packageName));
return v;
}
}
private class KnownBackend {
private String packageName;
private String simpleName;
private Drawable icon;
public KnownBackend(String packageName, String simpleName, Drawable icon) {
this.packageName = packageName;
this.simpleName = simpleName;
this.icon = icon;
}
@Override
public String toString() {
return simpleName;
}
}
}