/* * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Codename One designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Codename One through http://www.codenameone.com/ if you * need additional information or have any questions. */ package com.codename1.location; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import com.codename1.impl.android.AndroidImplementation; import com.codename1.impl.android.AndroidNativeUtil; import com.codename1.impl.android.LifecycleListener; import com.codename1.ui.Display; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.Geofence; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import java.io.IOException; import java.util.ArrayList; /** * * @author Chen */ public class AndroidLocationPlayServiceManager extends com.codename1.location.LocationManager implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, com.google.android.gms.location.LocationListener, LifecycleListener { public static AndroidLocationPlayServiceManager inMemoryBackgroundLocationListener; private GoogleApiClient mGoogleApiClient; private LocationRequest locationRequest; private static AndroidLocationPlayServiceManager instance = new AndroidLocationPlayServiceManager(); public AndroidLocationPlayServiceManager() { } public static AndroidLocationPlayServiceManager getInstance() { return instance; } @Override public Location getCurrentLocation() throws IOException { Location l = getLastKnownLocation(); if (l == null) { throw new IOException("cannot retrieve location try later"); } return l; } @Override public Location getLastKnownLocation() { while (!getmGoogleApiClient().isConnected()) { try { Thread.sleep(300); } catch (Exception ex) { } } android.location.Location location = LocationServices.FusedLocationApi.getLastLocation(getmGoogleApiClient()); if (location != null) { return AndroidLocationManager.convert(location); } return null; } @Override protected void bindListener() { final Class bgListenerClass = getBackgroundLocationListener(); Thread t = new Thread(new Runnable() { @Override public void run() { //wait until the client is connected, otherwise the call to //requestLocationUpdates will fail while (!getmGoogleApiClient().isConnected()) { try { Thread.sleep(300); } catch (Exception ex) { } } Handler mHandler = new Handler(Looper.getMainLooper()); mHandler.post(new Runnable() { public void run() { LocationRequest r = locationRequest; com.codename1.location.LocationRequest request = getRequest(); if (request != null) { LocationRequest lr = LocationRequest.create(); if (request.getPriority() == com.codename1.location.LocationRequest.PRIORITY_HIGH_ACCUARCY) { lr.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); } else if (request.getPriority() == com.codename1.location.LocationRequest.PRIORITY_MEDIUM_ACCUARCY) { lr.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY); } else { lr.setPriority(LocationRequest.PRIORITY_LOW_POWER); } lr.setInterval(request.getInterval()); r = lr; } if (AndroidImplementation.getActivity() == null) { // we are in the background // Sometimes using regular locations in the background causes a crash // so we need to use the pending intent version. Context context = AndroidNativeUtil.getContext(); Intent intent = new Intent(context, BackgroundLocationHandler.class); //there is an bug that causes this to not to workhttps://code.google.com/p/android/issues/detail?id=81812 //intent.putExtra("backgroundClass", getBackgroundLocationListener().getName()); //an ugly workaround to the putExtra bug if (bgListenerClass != null) { intent.setData(Uri.parse("http://codenameone.com/a?" + bgListenerClass.getName())); } PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); inMemoryBackgroundLocationListener = AndroidLocationPlayServiceManager.this; LocationServices.FusedLocationApi.requestLocationUpdates(getmGoogleApiClient(), r, pendingIntent); } else { LocationServices.FusedLocationApi.requestLocationUpdates(getmGoogleApiClient(), r, AndroidLocationPlayServiceManager.this); } } }); } }); t.setUncaughtExceptionHandler(AndroidImplementation.exceptionHandler); t.start(); } @Override protected void clearListener() { final Class bgListenerClass = getBackgroundLocationListener(); Thread t = new Thread(new Runnable() { @Override public void run() { //mGoogleApiClient must be connected while (!getmGoogleApiClient().isConnected()) { try { Thread.sleep(300); } catch (Exception ex) { } } Handler mHandler = new Handler(Looper.getMainLooper()); mHandler.post(new Runnable() { public void run() { if (inMemoryBackgroundLocationListener != null) { Context context = AndroidNativeUtil.getContext(); Intent intent = new Intent(context, BackgroundLocationHandler.class); if (bgListenerClass != null) { intent.putExtra("backgroundClass", bgListenerClass.getName()); } PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); LocationServices.FusedLocationApi.removeLocationUpdates(getmGoogleApiClient(), pendingIntent); inMemoryBackgroundLocationListener = null; } else { LocationServices.FusedLocationApi.removeLocationUpdates(getmGoogleApiClient(), AndroidLocationPlayServiceManager.this); } } }); } }); t.setUncaughtExceptionHandler(AndroidImplementation.exceptionHandler); t.start(); } @Override protected void bindBackgroundListener() { final Class bgListenerClass = getBackgroundLocationListener(); if (bgListenerClass == null) { return; } Thread t = new Thread(new Runnable() { @Override public void run() { //wait until the client is connected, otherwise the call to //requestLocationUpdates will fail while (!getmGoogleApiClient().isConnected()) { try { Thread.sleep(300); } catch (Exception ex) { } } Handler mHandler = new Handler(Looper.getMainLooper()); mHandler.post(new Runnable() { public void run() { //don't be too aggressive for location updates in the background LocationRequest req = LocationRequest.create(); setupBackgroundLocationRequest(req); Context context = AndroidNativeUtil.getContext().getApplicationContext(); Intent intent = new Intent(context, BackgroundLocationHandler.class); //there is an bug that causes this to not to workhttps://code.google.com/p/android/issues/detail?id=81812 //intent.putExtra("backgroundClass", getBackgroundLocationListener().getName()); //an ugly workaround to the putExtra bug intent.setData(Uri.parse("http://codenameone.com/a?" + bgListenerClass.getName())); PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); LocationServices.FusedLocationApi.requestLocationUpdates(getmGoogleApiClient(), req, pendingIntent); } }); } }); t.setUncaughtExceptionHandler(AndroidImplementation.exceptionHandler); t.start(); } private void setupBackgroundLocationRequest(LocationRequest req) { Display d = Display.getInstance(); String priorityStr = d.getProperty("android.backgroundLocation.priority", "PRIORITY_BALANCED_POWER_ACCURACY"); String fastestIntervalStr = d.getProperty("android.backgroundLocation.fastestInterval", "5000"); String intervalStr = d.getProperty("android.backgroundLocation.interval", "10000"); String smallestDisplacementStr = d.getProperty("android.backgroundLocation.smallestDisplacement", "50"); int priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY; if ("PRIORITY_HIGH_ACCURACY".equals(priorityStr)) { priority = LocationRequest.PRIORITY_HIGH_ACCURACY; } else if ("PRIORITY_LOW_POWER".equals(priorityStr)) { priority = LocationRequest.PRIORITY_LOW_POWER; } else if ("PRIORITY_NO_POWER".equals(priorityStr)) { priority = LocationRequest.PRIORITY_NO_POWER; } long interval = Long.parseLong(intervalStr); long fastestInterval = Long.parseLong(fastestIntervalStr); int smallestDisplacement = Integer.parseInt(smallestDisplacementStr); req.setPriority(priority) .setFastestInterval(fastestInterval) .setInterval(interval) .setSmallestDisplacement(smallestDisplacement); } @Override protected void clearBackgroundListener() { final Class bgListenerClass = getBackgroundLocationListener(); if (bgListenerClass == null) { return; } Thread t = new Thread(new Runnable() { @Override public void run() { //mGoogleApiClient must be connected while (!getmGoogleApiClient().isConnected()) { try { Thread.sleep(300); } catch (Exception ex) { } } Handler mHandler = new Handler(Looper.getMainLooper()); mHandler.post(new Runnable() { public void run() { Context context = AndroidNativeUtil.getContext().getApplicationContext(); Intent intent = new Intent(context, BackgroundLocationHandler.class); intent.putExtra("backgroundClass", bgListenerClass.getName()); PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); LocationServices.FusedLocationApi.removeLocationUpdates(getmGoogleApiClient(), pendingIntent); } }); } }); t.setUncaughtExceptionHandler(AndroidImplementation.exceptionHandler); t.start(); } @Override public void onLocationChanged(final android.location.Location loc) { synchronized (this) { final com.codename1.location.LocationListener l = getLocationListener(); if (l != null) { Display.getInstance().callSerially(new Runnable() { @Override public void run() { Location lastLocation = AndroidLocationManager.convert(loc); l.locationUpdated(lastLocation); } }); } } } @Override public void onConnected(Bundle bundle) { locationRequest = LocationRequest.create(); locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); locationRequest.setInterval(1000); // Update location every second setLocationManagerStatus(AVAILABLE); } @Override public void onConnectionSuspended(int i) { setLocationManagerStatus(TEMPORARILY_UNAVAILABLE); } @Override public void onConnectionFailed(ConnectionResult cr) { setLocationManagerStatus(OUT_OF_SERVICE); } private void setLocationManagerStatus(final int status) { int current = getStatus(); if (current != status) { setStatus(status); synchronized (this) { Display.getInstance().callSerially(new Runnable() { @Override public void run() { com.codename1.location.LocationListener l = getLocationListener(); if (l != null) { l.providerStateChanged(status); } } }); } } } @Override public void addGeoFencing(final Class GeofenceListenerClass, final com.codename1.location.Geofence gf) { Thread t = new Thread(new Runnable() { @Override public void run() { //wait until the client is connected, otherwise the call to //requestLocationUpdates will fail while (!getmGoogleApiClient().isConnected()) { try { Thread.sleep(300); } catch (Exception ex) { } } Handler mHandler = new Handler(Looper.getMainLooper()); mHandler.post(new Runnable() { public void run() { Context context = AndroidNativeUtil.getContext(); Intent intent = new Intent(context, GeofenceHandler.class); intent.putExtra("geofenceClass", GeofenceListenerClass.getName()); intent.putExtra("geofenceID", gf.getId()); PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); ArrayList<Geofence> geofences = new ArrayList<Geofence>(); geofences.add(new Geofence.Builder() .setRequestId(gf.getId()) .setCircularRegion(gf.getLoc().getLatitude(), gf.getLoc().getLongitude(), gf.getRadius()) .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT) .setExpirationDuration(gf.getExpiration()) .build()); LocationServices.GeofencingApi.addGeofences(getmGoogleApiClient(), geofences, pendingIntent); } }); } }); t.setUncaughtExceptionHandler(AndroidImplementation.exceptionHandler); t.start(); } /** * Stop tracking a Geofence if isGeofenceSupported() returns false this * method does nothing * * @param id a Geofence id to stop tracking */ public void removeGeoFencing(final String id) { Thread t = new Thread(new Runnable() { @Override public void run() { //wait until the client is connected, otherwise the call to //requestLocationUpdates will fail while (!getmGoogleApiClient().isConnected()) { try { Thread.sleep(300); } catch (Exception ex) { } } Handler mHandler = new Handler(Looper.getMainLooper()); mHandler.post(new Runnable() { public void run() { ArrayList<String> ids = new ArrayList<String>(); ids.add(id); LocationServices.GeofencingApi.removeGeofences(getmGoogleApiClient(), ids); } }); } }); t.setUncaughtExceptionHandler(AndroidImplementation.exceptionHandler); t.start(); } /** * LifeCycle methods * */ @Override public void onCreate(Bundle savedInstanceState) { } @Override public void onResume() { getmGoogleApiClient(); // This should initialize it if it isn't already. } @Override public void onPause() { } @Override public void onDestroy() { if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { mGoogleApiClient.disconnect(); } if (mGoogleApiClient != null) { mGoogleApiClient = null; } } @Override public void onSaveInstanceState(Bundle b) { } @Override public void onLowMemory() { } @Override public boolean isGPSDetectionSupported() { return true; } @Override public boolean isBackgroundLocationSupported() { return true; } @Override public boolean isGeofenceSupported() { return true; } @Override public boolean isGPSEnabled() { Context context = AndroidNativeUtil.getContext(); android.location.LocationManager locationManager = (android.location.LocationManager) context.getSystemService(Context.LOCATION_SERVICE); return locationManager.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER); } /** * @return the mGoogleApiClient */ private GoogleApiClient getmGoogleApiClient() { if (mGoogleApiClient == null) { mGoogleApiClient = new GoogleApiClient.Builder(AndroidNativeUtil.getContext()) .addApi(LocationServices.API) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .build(); if (!mGoogleApiClient.isConnected()) { mGoogleApiClient.connect(); } } return mGoogleApiClient; } /** * @param mGoogleApiClient the mGoogleApiClient to set */ private void setmGoogleApiClient(GoogleApiClient mGoogleApiClient) { this.mGoogleApiClient = mGoogleApiClient; } }