I am working on GeoFencing in Android and I am stuck at one point. My task is to show notification to user when he enters/exits a Geofence area defined by me.
Here is my code:
Activity class
public class TestMapActivity extends FragmentActivity implements
OnMarkerDragListener,ConnectionCallbacks, OnConnectionFailedListener,OnAddGeofencesResultListener {
private static GoogleMap map;
private LocationClient mLocationClient;
private PendingIntent mGeofencePendingIntent;
private SimpleGeoFence fence;
private List<Geofence> mGeoList;
private LocationRequest localRequest;
private GeofenceReceiver mBroadcastReceiver;
private IntentFilter mIntentFilter;
#Override
protected void onCreate(Bundle saveInstance)
{
super.onCreate(saveInstance);
setContentView(R.layout.activity_map);
map = ((SupportMapFragment)getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
map.setOnMarkerDragListener(this);
CameraPosition INIT =
new CameraPosition.Builder()
.target(new LatLng(19.0222, 72.8666))
.zoom(17.5F)
.bearing(300F) // orientation
.tilt( 50F) // viewing angle
.build();
map.moveCamera( CameraUpdateFactory.newCameraPosition(INIT) );
}
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.add_fence:
Toast.makeText(TestMapActivity.this, "Add fence is Selected", Toast.LENGTH_LONG).show();
fence= new SimpleGeoFence();
fence.toGeofence();
addMarkerForFence(fence);
addIntentForFence();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void addMarkerForFence(SimpleGeoFence fence){
if(fence == null){
// display an error message and return
return;
}
//Instantiates a new CircleOptions object + center/radius
CircleOptions circleOptions = new CircleOptions()
.center( new LatLng(19.0216, 72.8646 ))
.radius( 500 )
.fillColor(0x40ff0000)
.strokeColor(Color.TRANSPARENT)
.strokeWidth(2);
map.addCircle(circleOptions);
map.addMarker( new MarkerOptions()
.position( new LatLng(19.0216, 72.8646) )
.title("Fence " +fence.getId())
.snippet("Radius: " +fence.getRadius()) ).showInfoWindow();
// Get back the mutable Circle
Circle circle = map.addCircle(circleOptions);
}
public void addIntentForFence()
{
Geofence geoFence= fence.toGeofence();
mGeoList = new ArrayList<Geofence>();
mGeoList.add(geoFence);
mLocationClient = new LocationClient(this, this, this);
mLocationClient.connect();
}
#Override
public void onConnected(Bundle arg0) {
// TODO Auto-generated method stub
mGeofencePendingIntent = createRequestPendingIntent();
localRequest = LocationRequest.create();
localRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
localRequest.setInterval(5000);
mLocationClient.addGeofences(mGeoList, mGeofencePendingIntent , this);
}
private PendingIntent createRequestPendingIntent() {
if (null != mGeofencePendingIntent) {
return mGeofencePendingIntent;
} else {
Intent intent = new Intent("com.example.ACTION_RECEIVE_GEOFENCE");
System.out.println("Intent" +intent);
//sendBroadcast(intent);
return PendingIntent.getBroadcast(
getApplicationContext(),
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
}
} //end oncreate
GeofenceReceiver class
public class GeofenceReceiver extends BroadcastReceiver
{
public Context context;
Intent broadcastIntent = new Intent();
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
this.context = context;
broadcastIntent.addCategory("com.example.CATEGORY_LOCATION_SERVICES");
String action= intent.getAction();
if (LocationClient.hasError(intent)) {
//do something
}
else
{
handleEnterExit(intent);
}
}
private void handleEnterExit(Intent intent) {
int transition = LocationClient.getGeofenceTransition(intent);
System.out.println("transition" +transition); //getting -1 over here
if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER)
|| (transition == Geofence.GEOFENCE_TRANSITION_EXIT)) {
//send notification over here
}
}
AndroidManifest.xml container Receiver
<receiver android:name="com.example.GeofenceReceiver" android:exported="false">
<intent-filter >
<action android:name="com.example.ACTION_RECEIVE_GEOFENCE"/>
</intent-filter>
</receiver>
So basically I am able to see my GeoFence getting created but i don't get notification for the same.
does anyone have a solution for this?
Just check it sample code. u r taking Geo-fencing but you are not mention location client and location connect so that google play service not connect to client and you are not able to receive notification take google requester file and check main acitvity where geofence add in list and also connect location client hope so its work for you
So from the comments you are saying that you are able to add a geofence but you are not getting a notification. Firstly it looks as if you have used the android's geofence sample code which is their on their website and you have changed it so it uses a receiver instead of a service. For me i did the same and this might not seem like the right solution my answer is :
1.) Follow the same steps/code in the google'e geofence : http://developer.android.com/training/location/geofencing.html
2.) since you are changing the service to a receiver , in your createRequestPendingIntent() method are you returning the right broadcast intent ? I mean you have this line but is it reaching there ? is something getting returned there ?
return PendingIntent.getBroadcast(
mActivity,0,intent, PendingIntent.FLAG_UPDATE_CURRENT);
3.)Do you have a LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, mIntentFilter); in your onResume() ?
I would follow the google's sample project again and just make sure you are changing the service to receiver part right and you should be fine.
I had the same problem.
I think that's because you are using the application context for your pending intent, try to use the activity instead.
Related
I use a started service with the fused api, and implement the location listener directly on it.
The Location keeps updating even when the screen is locked, But it stops if the screen goes off.
So, is there any way to make sure that the location will keep updating when the screen is off?
I read a lot of other questions and I don't really know what i'm missing.
public class CLocationService extends Service implements GoogleApiClient.ConnectionCallbacks, LocationListener,
GoogleApiClient.OnConnectionFailedListener {
private GoogleApiClient mGoogleApiClient;
private PowerManager.WakeLock mWakeLock;
private LocationRequest mLocationRequest;
// Flag that indicates if a request is underway.
private boolean mInProgress;
private Boolean servicesAvailable = false;
private boolean isStarted;
public static final int LOCATION_SERVICE_NOTIFICATION_ID = 4567654;
private void showNotification() {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification = new Notification.Builder(this)
.setContentTitle(getText(R.string.app_name))
.setContentText("")
.setSmallIcon(R.mipmap.ic_notification)
.setContentIntent(pendingIntent)
.setTicker("")
.build();
startForeground(LOCATION_SERVICE_NOTIFICATION_ID, notification);
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
/*
* Create a new location client, using the enclosing class to
* handle callbacks.
*/
setUpLocationClientIfNeeded();
startLocationServices();
}
/*
* Create a new location client, using the enclosing class to
* handle callbacks.
*/
protected synchronized void buildGoogleApiClient() {
this.mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
private boolean servicesConnected() {
// Check that Google Play services is available
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
// If Google Play services is available
if (ConnectionResult.SUCCESS == resultCode) {
return true;
} else {
return false;
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
PowerManager mgr = (PowerManager) getSystemService(Context.POWER_SERVICE);
/*
WakeLock is reference counted so we don't want to create multiple WakeLocks. So do a check before initializing and acquiring.
This will fix the "java.lang.Exception: WakeLock finalized while still held: MyWakeLock" error that you may find.
*/
if (this.mWakeLock == null) { //**Added this
this.mWakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock");
}
if (!this.mWakeLock.isHeld()) { //**Added this
this.mWakeLock.acquire();
}
if (!servicesAvailable || mGoogleApiClient.isConnected() || mInProgress)
return START_STICKY;
setUpLocationClientIfNeeded();
if (!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting() && !mInProgress) {
mInProgress = true;
mGoogleApiClient.connect();
}
return START_STICKY;
}
private void setUpLocationClientIfNeeded() {
if (mGoogleApiClient == null)
buildGoogleApiClient();
}
#Override
public void onDestroy() {
stopLocationServices();
super.onDestroy();
}
private void startLocationServices() {
mInProgress = false;
// Create the LocationRequest object
mLocationRequest = LocationRequest.create();
// Use high accuracy
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
// Set the update interval to 5 seconds
mLocationRequest.setInterval(5000);
// Set the fastest update interval to 1 second
mLocationRequest.setFastestInterval(1000);
mLocationRequest.setSmallestDisplacement(0);
servicesAvailable = servicesConnected();
}
private void stopLocationServices() {
// Turn off the request flag
this.mInProgress = false;
if (this.servicesAvailable && this.mGoogleApiClient != null) {
this.mGoogleApiClient.unregisterConnectionCallbacks(this);
this.mGoogleApiClient.unregisterConnectionFailedListener(this);
this.mGoogleApiClient.disconnect();
// Destroy the current location client
this.mGoogleApiClient = null;
}
// Display the connection status
// Toast.makeText(this, DateFormat.getDateTimeInstance().format(new Date()) + ":
// Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show();
if (this.mWakeLock != null) {
this.mWakeLock.release();
this.mWakeLock = null;
}
}
private void cancelNotification() {
NotificationManager nMgr = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
nMgr.cancel(LOCATION_SERVICE_NOTIFICATION_ID);
}
#Override
public void onLocationChanged(Location location) {
// log the new location
}
#Override
public void onConnected(#Nullable Bundle bundle) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
LocationServices.FusedLocationApi.requestLocationUpdates(this.mGoogleApiClient,
mLocationRequest, this); // This is the changed line.
}
#Override
public void onConnectionSuspended(int i) {
// Turn off the request flag
mInProgress = false;
// Destroy the current location client
mGoogleApiClient = null;
}
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
mInProgress = false;
/*
* Google Play services can resolve some errors it detects.
* If the error has a resolution, try sending an Intent to
* start a Google Play services activity that can resolve
* error.
*/
if (connectionResult.hasResolution()) {
// If no resolution is available, display an error dialog
} else {
}
}
}
I'm not sure I have your answer but since you're 9 days in with nothing I'll give some suggestions.
My app is doing what you would like to do. I use a long running started Service to keep location updated even when the phone is off.
The difference most likely to cause different behavior between your code & mine is the return from onStartCommand(). You are returning START_STICKY. This is the recommended return for something like this:
This mode makes sense for things that will be explicitly started and
stopped to run for arbitrary periods of time, such as a service
performing background music playback.
However, I'm sending info in the Intent that I needed to have redelivered so I'm returning START_REDELIVER_INTENT. Try this (even if you have no need to redeliver any data) to see if it fixes your problem.
Also, I didn't need WakeLock in my implementation. Maybe your implementation needs this though. Have you tried without it?
Edit: Lastly, what kind of device are you using? link
Today I have a problem in my Android project. I use a Service with a thread in it to log location information in a period of 10s. However, when I change the screen orientation (Portrait -> Landscape), the period just messed up.
I think I may run another thread so that I got one more thread running behind once I rotate the screen. I have print log messages and it seems my guessing is right.
Here is my code:
public class LocationService extends Service
{
public Location loc;
public LocationService()
{
}
#Override
public int onStartCommand(Intent intent, int flags, int id)
{
Thread thread = new Thread(new Runnable()
{
#Override
public void run()
{
LocationManager locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
if ( ContextCompat.checkSelfPermission(LocationService.this, android.Manifest.permission.ACCESS_COARSE_LOCATION ) == PackageManager.PERMISSION_GRANTED )
{
loc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if(loc == null) // fall back to network if GPS is not available
{
loc = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
if(loc != null)
{
Timer timer = new Timer();
final String time = (new Date()).toString();
timer.scheduleAtFixedRate(new TimerTask()
{
#Override
public void run()
{
Log.d(time, "hehe");
double currentLat = loc.getLatitude();
double currentLng = loc.getLongitude();
Intent done = new Intent();
done.setAction("location");
done.putExtra("currentLat", currentLat);
done.putExtra("currentLng", currentLng);
sendBroadcast(done);
//Toast.makeText(LocationService.this, String.valueOf(currentLat) + String.valueOf(currentlng), Toast.LENGTH_LONG).show();
}
}, 10000, 10000);
}
}
else
{
Toast.makeText(LocationService.this, "Please allow app to access your location", Toast.LENGTH_LONG).show();
}
}
});
thread.start();
return START_STICKY; // stay running
}
#Override
public IBinder onBind(Intent intent)
{
// // TODO: Return the communication channel to the service.
// throw new UnsupportedOperationException("Not yet implemented");
return null;
}
// #Override
// public void onDestroy()
// {
// Log.d("hehe","onDestroy");
// super.onDestroy();
// }
}
Here is the code for activity:
public class MainActivity extends AppCompatActivity
{
private Toolbar toolbar;
private FragmentManager fragmentManager;
private LocalFragment localFragment;
private ServerFragment serverFragment;
private QueryFragment queryFragment;
private FragmentTransaction transaction;
public SQLiteHelper dbHelper;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new SQLiteHelper(this);
//garb handlers
fragmentManager = getFragmentManager();
localFragment = (LocalFragment) fragmentManager.findFragmentById(R.id.fragment_local);
serverFragment = (ServerFragment) fragmentManager.findFragmentById(R.id.fragment_server);
queryFragment = (QueryFragment) fragmentManager.findFragmentById(R.id.fragment_query);
// initial visibility
transaction = fragmentManager.beginTransaction();
if(this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
{
// toolbar (must be put in checking orientation because landscape layout does not have toolbar here)
toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle(R.string.toolbar_title);
setSupportActionBar(toolbar);
transaction.show(localFragment);
transaction.hide(serverFragment);
transaction.hide(queryFragment);
transaction.commit();
}
else // ORIENTATION_LANDSCAPE
{
transaction.hide(queryFragment); // landscape orientation does not need query function (?)
transaction.show(localFragment);
transaction.show(serverFragment);
transaction.commit();
}
// register network status receiver
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
NetworkStatusReceiver myNetworkReceiver = new NetworkStatusReceiver();
registerReceiver(myNetworkReceiver, intentFilter);
// start location service
Intent intent = new Intent(this, LocationService.class);
intent.setAction("location");
startService(intent);
// register location receiver
IntentFilter intentFilterLocation = new IntentFilter();
intentFilterLocation.addAction("location");
LocationReceiver myLocationReceiver = new LocationReceiver();
registerReceiver(myLocationReceiver, intentFilterLocation);
}
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_main_activity, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
transaction = fragmentManager.beginTransaction();
// handle click event
if(item.getItemId() == R.id.action_online)
{
transaction.hide(localFragment);
transaction.hide(queryFragment);
transaction.show(serverFragment);
transaction.commit();
}
else if(item.getItemId() == R.id.action_offline)
{
transaction.hide(serverFragment);
transaction.hide(queryFragment);
transaction.show(localFragment);
transaction.commit();
}
else // Query
{
transaction.hide(localFragment);
transaction.hide(serverFragment);
transaction.show(queryFragment);
transaction.commit();
}
return super.onOptionsItemSelected(item);
}
// receiver for network change action
private class NetworkStatusReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) // if receive network change event broadcast
{
Toast.makeText(context, "Network status changed!", Toast.LENGTH_LONG).show();
// why I cannot use another thread to do so? CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
// Thread thread = new Thread(new Runnable()
// {
// #Override
// public void run()
// {
int type = 0;
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); // get current network type
if (networkInfo != null && networkInfo.isAvailable())
{
type = networkInfo.getType();
String typeName = networkInfo.getTypeName(); // (?)
//serverFragment = (ServerFragment)fragmentManager.findFragmentById(R.id.fragment_server);
if (type == ConnectivityManager.TYPE_WIFI) // wifi
{
WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
Log.d("wifiInfo", wifiInfo.toString());
Log.d("SSID",wifiInfo.getSSID());
serverFragment.setNetworkStatusText("WIFI: " + wifiInfo.getSSID()); // thread issues here. WTF
} else if (type == ConnectivityManager.TYPE_MOBILE) // Cellar
{
serverFragment.setNetworkStatusText("Mobile Data");
} else // no network
{
serverFragment.setNetworkStatusText("No Network");
}
} else // no network
{
serverFragment.setNetworkStatusText("No Network");
}
// }
// });
// thread.start();
}
}
}
// receiver for location per 10s
public class LocationReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if(action.equals("location"))
{
double currentLat = intent.getDoubleExtra("currentLat", 0.0);
double currentLng = intent.getDoubleExtra("currentLng", 0.0);
Date now = new Date();
localFragment.addNewLocation(now, currentLat, currentLng); // add to list for listview
// TODO: 11/5/16 implement SQLite insert
dbHelper.addEntry(now, currentLat, currentLng);
Toast.makeText(MainActivity.this, "Current Location: " + String.valueOf(currentLat) + ", " + String.valueOf(currentLng), Toast.LENGTH_LONG).show();
}
}
}
public void setNetworkStatus(String networkStatus)
{
serverFragment.setNetworkStatusText(networkStatus);
}
}
My Question are:
It seems that the onDestroy () method would not be called when rotate the screen?
A new Service will not be created when rotate the screen?
How can I stop the thread created previously? Or what is the best way to handle this problem?
It seems that the onDestroy() method would not be called when rotate the screen?
No, it isn't. Changing the screen orientation kills the Activity and creates a new one, but your Service is still running. Screen orientation has no effect on this.
A new Service will not be created when rotate the screen?
No. A Service is essentially a singleton. Android will not create a new instance of your Service if there is already one running. However, onStartCommand() will be called again because your Activity calls startService() when it is created.
How can I stop the thread created previously? Or what is the best way to handle this problem?
The easiest way to deal with this is to check in onStartCommand() if your thread is already running. If so, you don't need to start it again. Save a reference to your Thread in a member variable (a field) in your Service and call isAlive() on it to see if it is running.
Also, in onDestroy() you should make sure that your Thread shuts down, otherwise it will continue to run even after your Service is dead. To do that you should create a boolean member variable (field) in the Thread, which you check in each loop. In onDestroy() of your Service, set that boolean so that the Thread exits.
i'm trying to use FusedLocationApi with pending intent to get period location updates so i can send the data to some server for processing. Everything works. However, there are some instances where the broadcast just stops receiving. I would need to restart the service again in order for it to continue.
I already have the service onStartCommand to return START_STICKY so even if the app is killed it should start the service again. Furthermore, I also added a Boot Completed Intent Receiver so if the phone died and user restarted phone it would restart my service.
So it seems everything is fine and working but just at some point, everything just stops. I did notice a few times that when it does stop working, the last location i received was NULL (i log every location update and error messages throughout my project).
Any ideas why location services just stops working?
P.S. there is no connection failure because i put a message and hook into that function and it's not being called. And it's not internet failure either as i log that as well. Once internet is restored it would continue as normal/expected.
Thanks.
This is the main activity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayShowHomeEnabled(false);
actionBar.setDisplayShowTitleEnabled(false);
if (findViewById(android.R.id.home) != null) {
findViewById(android.R.id.home).setVisibility(View.GONE);
}
LayoutInflater inflator = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflator.inflate(R.layout.header_logo, null);
ActionBar.LayoutParams params = new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT, ActionBar.LayoutParams.WRAP_CONTENT, Gravity.CENTER);
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setCustomView(view, params);
}
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.status_text);
// init preferences and status handler
MyStatusHandler.init(getApplicationContext(), textView);
// init web service call class
...;
// check for location services
if (!isLocationEnabled(getApplicationContext())){
String msg = "Location services not turned on";
MyStatusHandler.setStatusText(msg);
}
}
public static boolean isLocationEnabled(Context context) {
int locationMode = 0;
String locationProviders;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
try {
locationMode = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
}
return locationMode != Settings.Secure.LOCATION_MODE_OFF;
}else{
locationProviders = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
return !TextUtils.isEmpty(locationProviders);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
// create new intent activity for the settings page
Intent i = new Intent(this, MySettingsActivity.class);
startActivity(i);
return true;
}
else if (id == R.id.action_about){
// 1. Instantiate an AlertDialog.Builder with its constructor
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// 2. Chain together various setter methods to set the dialog characteristics
if (Constants.DEBUG_BUILD == true) {
builder.setMessage("v." + MyStatusHandler.getReleaseVersionNum() + " dev Build: " + MyStatusHandler.getDevVersionNum())
.setTitle("About")
.setCancelable(false)
.setPositiveButton("OK", null);
}
else{
builder.setMessage("v." + MyStatusHandler.getReleaseVersionNum())
.setTitle("About")
.setCancelable(false)
.setPositiveButton("OK", null);
}
// 3. Get the AlertDialog from create()
AlertDialog dialog = builder.create();
// show it
dialog.show();
return true;
}
return super.onOptionsItemSelected(item);
}
// check email entered
public boolean isSettingsEntered(){
boolean result = true;
if (MyStatusHandler.getEmailText().equals("") || MyStatusHandler.getPasswordText().equals("")){
// 1. Instantiate an AlertDialog.Builder with its constructor
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// 2. Chain together various setter methods to set the dialog characteristics
builder.setMessage("Please ensure both email and password are entered in settings")
.setTitle("Email and/or Password not set")
.setCancelable(false)
.setPositiveButton("OK",null);
// 3. Get the AlertDialog from create()
AlertDialog dialog = builder.create();
// show it
dialog.show();
result = false;
}
return result;
}
/** Called when the user clicks the Opt in button */
public void startService(View view) {
// Do something in response to button
if (isSettingsEntered() && isLocationEnabled(getApplicationContext())) {
// send opt in to web service
...;
// start service
startService(new Intent(this, BackgroundLocationService.class));
// update status text
String msg = "Connecting ...";
MyStatusHandler.setStatusText(msg);
}
}
/** Called when the user clicks the Opt out button */
public void stopService(View view) {
// Do something in response to button
if (isSettingsEntered() && isLocationEnabled(getApplicationContext())) {
// send OptOut to web service
...;
// update status text
String msg = "Connecting ...";
MyStatusHandler.setStatusText(msg);
}
}
public static void ...(boolean isOptIn, Location location, boolean sendOptIn){
if (sendOptIn)
{
// send opt in via async task
}
else{
// send location via async task
}
}
// Handle results returned to the FragmentActivity by Google Play services
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Decide what to do based on the original request code
switch (requestCode) {
case Constants.CONNECTION_FAILURE_RESOLUTION_REQUEST :
// log the error
MyStatusHandler.logDataToFile("Connection Failure Resolution Request - Result Code: "+String.valueOf(resultCode));
// If the result code is Activity.RESULT_OK, try to connect again
switch (resultCode) {
case Activity.RESULT_OK :
// Try the request again
MyStatusHandler.logDataToFile("Attempting to re-start service");
// start service
startService(new Intent(this, BackgroundLocationService.class));
break;
}
}
}
}
Here is the background service:
public class BackgroundLocationService extends Service implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
LocationListener {
public static final String TAG = BackgroundLocationService.class.getSimpleName();
private GoogleApiClient mGoogleApiClient;
private boolean mInProgress;
private LocationRequest mLocationRequest;
public void onCreate(){
super.onCreate();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
#Override
public int onStartCommand (Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
if(mGoogleApiClient.isConnected() || mInProgress)
return START_STICKY;
if(!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting() && !mInProgress) {
mInProgress = true;
mGoogleApiClient.connect();
}
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public void onConnected(Bundle bundle) {
mLocationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setFastestInterval(Constants.FASTEST_INTERVAL)
.setInterval(Constants.UPDATE_INTERVAL);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0,
new Intent(this, MyLocationHandler.class),
PendingIntent.FLAG_CANCEL_CURRENT);
if (mGoogleApiClient.isConnected()) {
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, pendingIntent);
}
else{
MyStatusHandler.setStatusText("Google Client Failed");
}
}
#Override
public void onConnectionSuspended(int i) {
}
#Override
public void onLocationChanged(Location location) {
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
mInProgress = false;
if (connectionResult.hasResolution()) {
try {
// Start an Activity that tries to resolve the error
connectionResult.startResolutionForResult(null, Constants.CONNECTION_FAILURE_RESOLUTION_REQUEST);
// * Thrown if Google Play services canceled the original
// * PendingIntent
} catch (IntentSender.SendIntentException e) {
// Log the error
e.printStackTrace();
}
} else {
//* If no resolution is available, display a dialog to the
// * user with the error.
Log.i(TAG, "Location services connection failed with code " + connectionResult.getErrorCode());
MyStatusHandler.setStatusText("Location services connection failed with code " + connectionResult.getErrorCode());
}
}
#Override
public void onDestroy(){
mInProgress = false;
if (mGoogleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
mGoogleApiClient.disconnect();
}
super.onDestroy();
}
}
Here is the broadcast receiver:
public class MyLocationHandler extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
if (location != null && MyStatusHandler.getOptInStatus()) {
// debug messages
String msg = Double.toString(location.getLatitude()) + "," +
Double.toString(location.getLongitude());
Log.d("debug", msg);
// log location to file
MyStatusHandler.logDataToFile("Location: "+msg);
// send Location to web service
MainActivity....(MyStatusHandler.getOptInStatus(), location, false);
}
if (location == null){
MyStatusHandler.logDataToFile("Location == NULL!");
}
}
}
I have set the interval to 5minutes and Fast interval to 2minutes.
NOTE: i removed some function calls and code for the web service operations
Everything works and i get updates as expected but at some point in time, the broadcast receiver doesn't get anything. When i check the logs, OptInStatus doesn't change and the last location broadcast i received was NULL.
The other parts of my code to call the web service and handle the status messages doesn't touch the service or location requests.
I'm using the new FusedLocationService, but, despite I'm getting latitude and longitude while being indoors, I don't see GPS signal being aquired (small gps point in the notifications bar not appearing).
I'm using the example from here adapted to location service
What I don't understand is why GPS signal is not being searched for despite GPS is enabled (but I'm getting coordinates, I guess I getting those from wifi or cell id)
In my Application class I create a Service (this is a ServicesManager, which in turn creates another service (to retrieve locations). I'm sending as context to LocationClient the ServicesManager as it is a context (because it's a Service).
Thanks in advance. Guillermo.
UPDATE
If I turn off the Use Wireless Network option in location services on the phone while GPS is enabled I don't get locations at all. So something is happening with the FusedLocationService and the GPS.
I'll add code so it can be better understood
In Application class I'm using the LocalService example from: here
private ServicesManager mBoundService;
private boolean mIsBound;
private ServiceConnection mConnectionToServicesManager = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mBoundService = ((ServicesManager.LocalBinder)service).getService();
servicesManager = mBoundService;
}
public void onServiceDisconnected(ComponentName className) {
mBoundService = null;
}
};
void doBindServiceManagerService() {
bindService(new Intent(this, ServicesManager.class), mConnectionToServicesManager, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
unbindService(mConnectionToServicesManager);
mIsBound = false;
}
}
Then ServicesManager extends Service and in the constructor I'm writing this:
fusedLocationService = new FusedLocationService(this);
Then I call to:
fusedLocationService.startListeningLocationUpdates(this);
This is the implementation of startListeningLocationUpdates in FusedLocationService class
public boolean startListeningLocationUpdates(Context context) {
if (!GdpTesisApplication.IsGooglePlayServicesAvailable) {
return false;
}
mDetectionRequester.requestUpdates();
FusedLocationService.IsServiceRunning = true;
return true;
}
And requestUpdates() tries to connecto to GooglePlayServices
private void requestConnection() {
getFusedLocationClient().connect();
}
#Override
public void onConnected(Bundle arg0) {
continueRequestLocationUpdates();
}
private void continueRequestLocationUpdates() {
locationrequest = LocationRequest.create();
locationrequest.setInterval(LocationUtils.DETECTION_INTERVAL_MILLISECONDS);
getFusedLocationClient().requestLocationUpdates(locationrequest, createRequestPendingIntent());
}
private PendingIntent createRequestPendingIntent() {
if (null != getRequestPendingIntent()) {
return mFusedLocationPendingIntent;
} else {
Intent intent = new Intent(mContext, FusedLocationIntentService.class);
PendingIntent pendingIntent = PendingIntent.getService(mContext, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
setRequestPendingIntent(pendingIntent);
return pendingIntent;
}
}
Finally I have an IntentService which onHandleIntent extracts the location and shows it with:
Location location = intent.getParcelableExtra(LocationClient.KEY_LOCATION_CHANGED);
I don't know why GPS is not working. Any idea?
Take a look here:
http://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html#setPriority%28int%29
You didn't set a priority for your LocationRequest. Try with PRIORITY_HIGH_ACCURACY.
I have written a simple Gps Service. Now I am writing a testcase for testing it. I am trying to send a mock location but the onLocation changed is not getting called in my GpsService location listener.
Here is my GpsService
/**
* returns the binder object that client of this can bind to
*/
#Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
#Override
public void onCreate() {
super.onCreate();
Log.e("GpsService","StartService");
}
private class MyLocationListener implements LocationListener {
#Override
public void onLocationChanged(Location loc) {
Log.e("GpsService","Location in GpsService"+loc.toString());
if(loc.hasAccuracy() && loc.getAccuracy() <= minAccuracyMeters)
{
if(targetLocation != null)
{
float distance = loc.distanceTo(targetLocation);
Log.e("GpsService ","Location latitude +"+loc.getLatitude()+"longitude "+loc.getLongitude());
if(distance < 5.0)
Toast.makeText(getBaseContext(), "You have reached the target location", Toast.LENGTH_LONG).show();
}
}
}
public void setTargetLocation (Location _location){
targetLocation = _location;
}
/**
* Class for clients to access. Because we know this service always runs in
* the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
GpsService getService() {
return GpsService.this;
}
}
private final IBinder mBinder = new LocalBinder();
#Override
public void onDestroy() {
if(lm != null)
lm.removeUpdates(locationListener);
super.onDestroy();
}
//Setting up for test case
public void setUpProvider(String provider){
lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
locationListener = new MyLocationListener();
lm.requestLocationUpdates(provider,
minTimeMillis,
minDistanceMeters,
locationListener);
}
Now here is my TestCase
public class MockLocationTest extends ServiceTestCase {
private LocationManager locationManager = null;
private static final String MOCK_GPS_PROVIDER = "MOCK_GPS_PROVIDER";
private List<String> data = new ArrayList<String>();
public MockLocationTest(Class<GpsService> serviceClass) {
super(serviceClass);
}
public MockLocationTest() {
super(GpsService.class);
}
#Override
protected void setupService() {
super.setupService();
System.out.println("Service Set Up");
Intent intent = new Intent(getContext(),GpsService.class);
startService(intent);
assertNotNull("The Service should not be null",getService());
System.out.println("Service Started");
}
#Override
protected void setUp() throws Exception {
super.setUp();
}
#MediumTest
public void testBindable() {
Intent startIntent = new Intent();
startIntent.setClass(getContext(), GpsService.class);
IBinder service = bindService(startIntent);
assertNotNull("Bound to service ",service);
getService().setUpProvider(MOCK_GPS_PROVIDER);
Location demo = new Location(MOCK_GPS_PROVIDER);
demo.setLatitude(22.579937);
demo.setLongitude(88.486805);
getService().setTargetLocation(demo);
System.out.println("Test Bindable");
}
#SmallTest
public void testMockData(){
locationManager = (LocationManager) getContext().getSystemService((Context.LOCATION_SERVICE));
locationManager.setTestProviderEnabled(MOCK_GPS_PROVIDER, true);
Location location = new Location(MOCK_GPS_PROVIDER);
location.setLatitude(22.579937);
location.setLongitude(88.486805);
location.setTime(System.currentTimeMillis());
// show debug message in log
// provide the new location
locationManager.setTestProviderLocation(MOCK_GPS_PROVIDER, location);//send mock data
}
#Override
protected void tearDown() throws Exception {
super.tearDown();
}
}
Unfortunately I cant see any log in the onLocationChange callback in the service. The testcase runs successfully.
I have added the following in both my service and testservice's manifest
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
Can anybody help me here
I also tried putting this in one function
#LargeTest
public void testBindable() {
Intent startIntent = new Intent();
startIntent.setClass(getContext(), GpsService.class);
IBinder service = bindService(startIntent);
assertNotNull("Bound to service ",service);
LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
locationManager.addTestProvider(MOCK_GPS_PROVIDER, false, false,
false, false, false, false, false, 0, 5);
locationManager.setTestProviderEnabled(MOCK_GPS_PROVIDER, true);
getService().setUpProvider(MOCK_GPS_PROVIDER,locationManager);
Location demo = new Location(MOCK_GPS_PROVIDER);
demo.setLatitude(22.579937);
demo.setLongitude(88.486805);
demo.setTime(System.currentTimeMillis());
getService().setTargetLocation(demo);
Location narkeldanga = new Location(MOCK_GPS_PROVIDER);
narkeldanga.setLatitude(22.578986);
narkeldanga.setLongitude(88.470154);
narkeldanga.setTime(System.currentTimeMillis());
locationManager.setTestProviderLocation(MOCK_GPS_PROVIDER, narkeldanga);
try {
Thread.sleep(10000);
// gracefully handle Thread interruption (important!)
if(Thread.currentThread().isInterrupted())
throw new InterruptedException("");
} catch (InterruptedException e) {
}
locationManager.setTestProviderLocation(MOCK_GPS_PROVIDER, demo);
try {
Thread.sleep(10000);
// gracefully handle Thread interruption (important!)
if(Thread.currentThread().isInterrupted())
throw new InterruptedException("");
} catch (InterruptedException e) {
}
System.out.println("Test Bindable");
}
but to no avail.
This is a problem I and others have encountered in jUnit tests. Namely, testBindable() will run in one thread while your service is running in another. The result is that the location service is still setting up and hasn't even thought about using your callbacks when the test hits the end and jUnit clears everything from memory. I am still looking for a good recipe to follow for structuring such a test, but I can suggest a few things you could try. The simplest is just to put a sleep() in your test to give things time to process in the other threads. For example:
public void testBindable() throws Exception {
Intent startIntent = new Intent();
startIntent.setClass(getContext(), GpsService.class);
IBinder service = bindService(startIntent);
assertNotNull("Bound to service ",service);
getService().setUpProvider(MOCK_GPS_PROVIDER);
Location demo = new Location(MOCK_GPS_PROVIDER);
demo.setLatitude(22.579937);
demo.setLongitude(88.486805);
getService().setTargetLocation(demo);
TimeUnit.SECONDS.sleep(5);
System.out.println("Test Bindable");
}
This approach is not ideal for a number of reasons. 5 seconds (or whatever you put in) may not be enough sometimes or too much time in other cases.
Alternatively, you can use latches or other mechanisms to detect when your callbacks have executed. However, that approach complicates your code with latches (or you can use mutex locks) that are only used by the test and ideally should not be part of the actual application under test.
The value of MOCK_GPS_PROVIDER shouldn't be "flp" ?
http://developer.android.com/training/location/location-testing.html#TestData