commit 855ea472d34b481a7663a34025027fc93f4487d1 Author: mar-v-in Date: Wed Mar 5 21:31:46 2014 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4601963 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.iml +gen +bin diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..26f4d3c --- /dev/null +++ b/Android.mk @@ -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)) diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..80feb86 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..0941a94 --- /dev/null +++ b/README.md @@ -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` diff --git a/api/Android.mk b/api/Android.mk new file mode 100644 index 0000000..b1a427c --- /dev/null +++ b/api/Android.mk @@ -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) diff --git a/api/AndroidManifest.xml b/api/AndroidManifest.xml new file mode 100644 index 0000000..9adedee --- /dev/null +++ b/api/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/api/src/org/microg/nlp/api/LocationBackend.aidl b/api/src/org/microg/nlp/api/LocationBackend.aidl new file mode 100644 index 0000000..a91e0ab --- /dev/null +++ b/api/src/org/microg/nlp/api/LocationBackend.aidl @@ -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(); +} diff --git a/api/src/org/microg/nlp/api/LocationBackendService.java b/api/src/org/microg/nlp/api/LocationBackendService.java new file mode 100644 index 0000000..0fcd7af --- /dev/null +++ b/api/src/org/microg/nlp/api/LocationBackendService.java @@ -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. + *

+ * 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. + *

+ * 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; + } + } +} diff --git a/api/src/org/microg/nlp/api/LocationCallback.aidl b/api/src/org/microg/nlp/api/LocationCallback.aidl new file mode 100644 index 0000000..9305acd --- /dev/null +++ b/api/src/org/microg/nlp/api/LocationCallback.aidl @@ -0,0 +1,7 @@ +package org.microg.nlp.api; + +import android.location.Location; + +interface LocationCallback { + void report(in Location location); +} diff --git a/api/src/org/microg/nlp/api/NlpApiConstants.java b/api/src/org/microg/nlp/api/NlpApiConstants.java new file mode 100644 index 0000000..1db32b5 --- /dev/null +++ b/api/src/org/microg/nlp/api/NlpApiConstants.java @@ -0,0 +1,5 @@ +package org.microg.nlp.api; + +public class NlpApiConstants { + public static final String ACTION_LOCATION_BACKEND = "org.microg.nlp.LOCATION_BACKEND"; +} diff --git a/res/drawable-hdpi/nlp_app_icon.png b/res/drawable-hdpi/nlp_app_icon.png new file mode 100644 index 0000000..1654d9c Binary files /dev/null and b/res/drawable-hdpi/nlp_app_icon.png differ diff --git a/res/drawable-mdpi/nlp_app_icon.png b/res/drawable-mdpi/nlp_app_icon.png new file mode 100644 index 0000000..2c1b3c5 Binary files /dev/null and b/res/drawable-mdpi/nlp_app_icon.png differ diff --git a/res/drawable-xhdpi/nlp_app_icon.png b/res/drawable-xhdpi/nlp_app_icon.png new file mode 100644 index 0000000..a793bac Binary files /dev/null and b/res/drawable-xhdpi/nlp_app_icon.png differ diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..aa4401b --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,4 @@ + + + NLP Controller + \ No newline at end of file diff --git a/sample/AndroidManifest.xml b/sample/AndroidManifest.xml new file mode 100644 index 0000000..6b34e2f --- /dev/null +++ b/sample/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/sample/res/drawable/icon.png b/sample/res/drawable/icon.png new file mode 100644 index 0000000..08ee50d Binary files /dev/null and b/sample/res/drawable/icon.png differ diff --git a/sample/res/values/strings.xml b/sample/res/values/strings.xml new file mode 100644 index 0000000..9a4ab78 --- /dev/null +++ b/sample/res/values/strings.xml @@ -0,0 +1,4 @@ + + + NetworkLocationV2-SamplePlugin + \ No newline at end of file diff --git a/sample/src/org/microg/nlp/api/sample/SampleBackendService.java b/sample/src/org/microg/nlp/api/sample/SampleBackendService.java new file mode 100644 index 0000000..8432900 --- /dev/null +++ b/sample/src/org/microg/nlp/api/sample/SampleBackendService.java @@ -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; + } +} diff --git a/src/org/microg/nlp/Provider.java b/src/org/microg/nlp/Provider.java new file mode 100644 index 0000000..889284c --- /dev/null +++ b/src/org/microg/nlp/Provider.java @@ -0,0 +1,7 @@ +package org.microg.nlp; + +import android.os.IBinder; + +public interface Provider { + IBinder getBinder(); +} diff --git a/src/org/microg/nlp/ProviderService.java b/src/org/microg/nlp/ProviderService.java new file mode 100644 index 0000000..32c7f8c --- /dev/null +++ b/src/org/microg/nlp/ProviderService.java @@ -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; + } +} diff --git a/src/org/microg/nlp/geocode/GeocodeProvider.java b/src/org/microg/nlp/geocode/GeocodeProvider.java new file mode 100644 index 0000000..7d6ae69 --- /dev/null +++ b/src/org/microg/nlp/geocode/GeocodeProvider.java @@ -0,0 +1,6 @@ +package org.microg.nlp.geocode; + +import org.microg.nlp.Provider; + +public interface GeocodeProvider extends Provider { +} diff --git a/src/org/microg/nlp/geocode/GeocodeProviderV1.java b/src/org/microg/nlp/geocode/GeocodeProviderV1.java new file mode 100644 index 0000000..842cbe4 --- /dev/null +++ b/src/org/microg/nlp/geocode/GeocodeProviderV1.java @@ -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

addresses) { + return null; + } + + @Override + public String onGetFromLocationName(String locationName, double lowerLeftLatitude, double lowerLeftLongitude, + double upperRightLatitude, double upperRightLongitude, int maxResults, + GeocoderParams params, List
addresses) { + return null; + } +} diff --git a/src/org/microg/nlp/geocode/GeocodeService.java b/src/org/microg/nlp/geocode/GeocodeService.java new file mode 100644 index 0000000..4a4853a --- /dev/null +++ b/src/org/microg/nlp/geocode/GeocodeService.java @@ -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); + } +} diff --git a/src/org/microg/nlp/geocode/GeocodeServiceV1.java b/src/org/microg/nlp/geocode/GeocodeServiceV1.java new file mode 100644 index 0000000..afaa2b9 --- /dev/null +++ b/src/org/microg/nlp/geocode/GeocodeServiceV1.java @@ -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(); + } +} diff --git a/src/org/microg/nlp/location/BackendHelper.java b/src/org/microg/nlp/location/BackendHelper.java new file mode 100644 index 0000000..e4e7935 --- /dev/null +++ b/src/org/microg/nlp/location/BackendHelper.java @@ -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)); + } + } +} diff --git a/src/org/microg/nlp/location/LocationProvider.java b/src/org/microg/nlp/location/LocationProvider.java new file mode 100644 index 0000000..2bfae99 --- /dev/null +++ b/src/org/microg/nlp/location/LocationProvider.java @@ -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); +} diff --git a/src/org/microg/nlp/location/LocationProviderV2.java b/src/org/microg/nlp/location/LocationProviderV2.java new file mode 100644 index 0000000..ea102ed --- /dev/null +++ b/src/org/microg/nlp/location/LocationProviderV2.java @@ -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(); + } + } +} diff --git a/src/org/microg/nlp/location/LocationService.java b/src/org/microg/nlp/location/LocationService.java new file mode 100644 index 0000000..0993169 --- /dev/null +++ b/src/org/microg/nlp/location/LocationService.java @@ -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; + } +} diff --git a/src/org/microg/nlp/location/LocationServiceV2.java b/src/org/microg/nlp/location/LocationServiceV2.java new file mode 100644 index 0000000..db3856b --- /dev/null +++ b/src/org/microg/nlp/location/LocationServiceV2.java @@ -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); + } +} diff --git a/src/org/microg/nlp/location/ThreadHelper.java b/src/org/microg/nlp/location/ThreadHelper.java new file mode 100644 index 0000000..8c3a2e0 --- /dev/null +++ b/src/org/microg/nlp/location/ThreadHelper.java @@ -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(); + } +} diff --git a/src/org/microg/nlp/ui/LocationBackendConfig.java b/src/org/microg/nlp/ui/LocationBackendConfig.java new file mode 100644 index 0000000..65c69e7 --- /dev/null +++ b/src/org/microg/nlp/ui/LocationBackendConfig.java @@ -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 activeBackends; + private Adapter adapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + activeBackends = new ArrayList(); + List backends = new ArrayList(); + Intent intent = new Intent(NlpApiConstants.ACTION_LOCATION_BACKEND); + List 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 { + public Adapter(List 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; + } + } +} \ No newline at end of file