Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ahmadmust8/724afd6f5a49e4593462a8d5c3997f94 to your computer and use it in GitHub Desktop.
Save ahmadmust8/724afd6f5a49e4593462a8d5c3997f94 to your computer and use it in GitHub Desktop.
Location Updates using a PendingIntent: Get updates about a device's location using a PendingIntent. Sample shows implementation using an IntentService as well as a BroadcastReceiver.
/**
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
package com.google.android.gms.location.sample.locationupdatespendingintent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.util.Log;
import com.google.android.gms.location.LocationResult;
import java.util.List;
/**
* Receiver for handling location updates.
*
* For apps targeting API level O
* {@link android.app.PendingIntent#getBroadcast(Context, int, Intent, int)} should be used when
* requesting location updates. Due to limits on background services,
* {@link android.app.PendingIntent#getService(Context, int, Intent, int)} should not be used.
*
* Note: Apps running on "O" devices (regardless of targetSdkVersion) may receive updates
* less frequently than the interval specified in the
* {@link com.google.android.gms.location.LocationRequest} when the app is no longer in the
* foreground.
*/
public class LocationUpdatesBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "LUBroadcastReceiver";
static final String ACTION_PROCESS_UPDATES =
"com.google.android.gms.location.sample.locationupdatespendingintent.action" +
".PROCESS_UPDATES";
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_PROCESS_UPDATES.equals(action)) {
LocationResult result = LocationResult.extractResult(intent);
if (result != null) {
List<Location> locations = result.getLocations();
Utils.setLocationUpdatesResult(context, locations);
Utils.sendNotification(context, Utils.getLocationResultTitle(context, locations));
Log.i(TAG, Utils.getLocationUpdatesResult(context));
}
}
}
}
}
/**
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
package com.google.android.gms.location.sample.locationupdatespendingintent;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.util.Log;
import com.google.android.gms.location.LocationResult;
import java.util.List;
/**
* Handles incoming location updates and displays a notification with the location data.
*
* For apps targeting API level 25 ("Nougat") or lower, location updates may be requested
* using {@link android.app.PendingIntent#getService(Context, int, Intent, int)} or
* {@link android.app.PendingIntent#getBroadcast(Context, int, Intent, int)}. For apps targeting
* API level O, only {@code getBroadcast} should be used.
*
* Note: Apps running on "O" devices (regardless of targetSdkVersion) may receive updates
* less frequently than the interval specified in the
* {@link com.google.android.gms.location.LocationRequest} when the app is no longer in the
* foreground.
*/
public class LocationUpdatesIntentService extends IntentService {
private static final String ACTION_PROCESS_UPDATES =
"com.google.android.gms.location.sample.locationupdatespendingintent.action" +
".PROCESS_UPDATES";
private static final String TAG = LocationUpdatesIntentService.class.getSimpleName();
public LocationUpdatesIntentService() {
// Name the worker thread.
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_PROCESS_UPDATES.equals(action)) {
LocationResult result = LocationResult.extractResult(intent);
if (result != null) {
List<Location> locations = result.getLocations();
Utils.setLocationUpdatesResult(this, locations);
Utils.sendNotification(this, Utils.getLocationResultTitle(this, locations));
Log.i(TAG, Utils.getLocationUpdatesResult(this));
}
}
}
}
}
/**
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
package com.google.android.gms.location.sample.locationupdatespendingintent;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.support.design.widget.Snackbar;
import android.Manifest;
import android.widget.Button;
import android.widget.TextView;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
/**
* The only activity in this sample. Displays UI widgets for requesting and removing location
* updates, and for the batched location updates that are reported.
*
* Location updates requested through this activity continue even when the activity is not in the
* foreground. Note: apps running on "O" devices (regardless of targetSdkVersion) may receive
* updates less frequently than the interval specified in the {@link LocationRequest} when the app
* is no longer in the foreground.
*/
public class MainActivity extends FragmentActivity implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = MainActivity.class.getSimpleName();
private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34;
/**
* The desired interval for location updates. Inexact. Updates may be more or less frequent.
*/
private static final long UPDATE_INTERVAL = 60000; // Every 60 seconds.
/**
* The fastest rate for active location updates. Updates will never be more frequent
* than this value, but they may be less frequent.
*/
private static final long FASTEST_UPDATE_INTERVAL = 30000; // Every 30 seconds
/**
* The max time before batched results are delivered by location services. Results may be
* delivered sooner than this interval.
*/
private static final long MAX_WAIT_TIME = UPDATE_INTERVAL * 5; // Every 5 minutes.
/**
* Stores parameters for requests to the FusedLocationProviderApi.
*/
private LocationRequest mLocationRequest;
/**
* The entry point to Google Play Services.
*/
private GoogleApiClient mGoogleApiClient;
// UI Widgets.
private Button mRequestUpdatesButton;
private Button mRemoveUpdatesButton;
private TextView mLocationUpdatesResultView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRequestUpdatesButton = (Button) findViewById(R.id.request_updates_button);
mRemoveUpdatesButton = (Button) findViewById(R.id.remove_updates_button);
mLocationUpdatesResultView = (TextView) findViewById(R.id.location_updates_result);
// Check if the user revoked runtime permissions.
if (!checkPermissions()) {
requestPermissions();
}
buildGoogleApiClient();
}
@Override
protected void onStart() {
super.onStart();
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onResume() {
super.onResume();
updateButtonsState(Utils.getRequestingLocationUpdates(this));
mLocationUpdatesResultView.setText(Utils.getLocationUpdatesResult(this));
}
@Override
protected void onStop() {
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this);
super.onStop();
}
/**
* Sets up the location request. Android has two location request settings:
* {@code ACCESS_COARSE_LOCATION} and {@code ACCESS_FINE_LOCATION}. These settings control
* the accuracy of the current location. This sample uses ACCESS_FINE_LOCATION, as defined in
* the AndroidManifest.xml.
* <p/>
* When the ACCESS_FINE_LOCATION setting is specified, combined with a fast update
* interval (5 seconds), the Fused Location Provider API returns location updates that are
* accurate to within a few feet.
* <p/>
* These settings are appropriate for mapping applications that show real-time location
* updates.
*/
private void createLocationRequest() {
mLocationRequest = new LocationRequest();
// Sets the desired interval for active location updates. This interval is
// inexact. You may not receive updates at all if no location sources are available, or
// you may receive them slower than requested. You may also receive updates faster than
// requested if other applications are requesting location at a faster interval.
// Note: apps running on "O" devices (regardless of targetSdkVersion) may receive updates
// less frequently than this interval when the app is no longer in the foreground.
mLocationRequest.setInterval(UPDATE_INTERVAL);
// Sets the fastest rate for active location updates. This interval is exact, and your
// application will never receive updates faster than this value.
mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
// Sets the maximum time when batched location updates are delivered. Updates may be
// delivered sooner than this interval.
mLocationRequest.setMaxWaitTime(MAX_WAIT_TIME);
}
/**
* Builds {@link GoogleApiClient}, enabling automatic lifecycle management using
* {@link GoogleApiClient.Builder#enableAutoManage(android.support.v4.app.FragmentActivity,
* int, GoogleApiClient.OnConnectionFailedListener)}. I.e., GoogleApiClient connects in
* {@link AppCompatActivity#onStart}, or if onStart() has already happened, it connects
* immediately, and disconnects automatically in {@link AppCompatActivity#onStop}.
*/
private void buildGoogleApiClient() {
if (mGoogleApiClient != null) {
return;
}
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.enableAutoManage(this, this)
.addApi(LocationServices.API)
.build();
createLocationRequest();
}
@Override
public void onConnected(@Nullable Bundle bundle) {
Log.i(TAG, "GoogleApiClient connected");
}
private PendingIntent getPendingIntent() {
// Note: for apps targeting API level 25 ("Nougat") or lower, either
// PendingIntent.getService() or PendingIntent.getBroadcast() may be used when requesting
// location updates. For apps targeting API level O, only
// PendingIntent.getBroadcast() should be used. This is due to the limits placed on services
// started in the background in "O".
// TODO(developer): uncomment to use PendingIntent.getService().
// Intent intent = new Intent(this, LocationUpdatesIntentService.class);
// intent.setAction(LocationUpdatesIntentService.ACTION_PROCESS_UPDATES);
// return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent intent = new Intent(this, LocationUpdatesBroadcastReceiver.class);
intent.setAction(LocationUpdatesBroadcastReceiver.ACTION_PROCESS_UPDATES);
return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
public void onConnectionSuspended(int i) {
final String text = "Connection suspended";
Log.w(TAG, text + ": Error code: " + i);
showSnackbar("Connection suspended");
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
final String text = "Exception while connecting to Google Play services";
Log.w(TAG, text + ": " + connectionResult.getErrorMessage());
showSnackbar(text);
}
/**
* Shows a {@link Snackbar} using {@code text}.
*
* @param text The Snackbar text.
*/
private void showSnackbar(final String text) {
View container = findViewById(R.id.activity_main);
if (container != null) {
Snackbar.make(container, text, Snackbar.LENGTH_LONG).show();
}
}
/**
* Return the current state of the permissions needed.
*/
private boolean checkPermissions() {
int permissionState = ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION);
return permissionState == PackageManager.PERMISSION_GRANTED;
}
private void requestPermissions() {
boolean shouldProvideRationale =
ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_FINE_LOCATION);
// Provide an additional rationale to the user. This would happen if the user denied the
// request previously, but didn't check the "Don't ask again" checkbox.
if (shouldProvideRationale) {
Log.i(TAG, "Displaying permission rationale to provide additional context.");
Snackbar.make(
findViewById(R.id.activity_main),
R.string.permission_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, new View.OnClickListener() {
@Override
public void onClick(View view) {
// Request permission
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_PERMISSIONS_REQUEST_CODE);
}
})
.show();
} else {
Log.i(TAG, "Requesting permission");
// Request permission. It's possible this can be auto answered if device policy
// sets the permission in a given state or the user denied the permission
// previously and checked "Never ask again".
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_PERMISSIONS_REQUEST_CODE);
}
}
/**
* Callback received when a permissions request has been completed.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
Log.i(TAG, "onRequestPermissionResult");
if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) {
if (grantResults.length <= 0) {
// If user interaction was interrupted, the permission request is cancelled and you
// receive empty arrays.
Log.i(TAG, "User interaction was cancelled.");
} else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission was granted. Kick off the process of building and connecting
// GoogleApiClient.
buildGoogleApiClient();
} else {
// Permission denied.
// Notify the user via a SnackBar that they have rejected a core permission for the
// app, which makes the Activity useless. In a real app, core permissions would
// typically be best requested during a welcome-screen flow.
// Additionally, it is important to remember that a permission might have been
// rejected without asking the user for permission (device policy or "Never ask
// again" prompts). Therefore, a user interface affordance is typically implemented
// when permissions are denied. Otherwise, your app could appear unresponsive to
// touches or interactions which have required permissions.
Snackbar.make(
findViewById(R.id.activity_main),
R.string.permission_denied_explanation,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.settings, new View.OnClickListener() {
@Override
public void onClick(View view) {
// Build intent that displays the App settings screen.
Intent intent = new Intent();
intent.setAction(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package",
BuildConfig.APPLICATION_ID, null);
intent.setData(uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
})
.show();
}
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
if (s.equals(Utils.KEY_LOCATION_UPDATES_RESULT)) {
mLocationUpdatesResultView.setText(Utils.getLocationUpdatesResult(this));
} else if (s.equals(Utils.KEY_LOCATION_UPDATES_REQUESTED)) {
updateButtonsState(Utils.getRequestingLocationUpdates(this));
}
}
/**
* Handles the Request Updates button and requests start of location updates.
*/
public void requestLocationUpdates(View view) {
try {
Log.i(TAG, "Starting location updates");
Utils.setRequestingLocationUpdates(this, true);
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, mLocationRequest, getPendingIntent());
} catch (SecurityException e) {
Utils.setRequestingLocationUpdates(this, false);
e.printStackTrace();
}
}
/**
* Handles the Remove Updates button, and requests removal of location updates.
*/
public void removeLocationUpdates(View view) {
Log.i(TAG, "Removing location updates");
Utils.setRequestingLocationUpdates(this, false);
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient,
getPendingIntent());
}
/**
* Ensures that only one button is enabled at any time. The Start Updates button is enabled
* if the user is not requesting location updates. The Stop Updates button is enabled if the
* user is requesting location updates.
*/
private void updateButtonsState(boolean requestingLocationUpdates) {
if (requestingLocationUpdates) {
mRequestUpdatesButton.setEnabled(false);
mRemoveUpdatesButton.setEnabled(true);
} else {
mRequestUpdatesButton.setEnabled(true);
mRemoveUpdatesButton.setEnabled(false);
}
}
}
/**
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
package com.google.android.gms.location.sample.locationupdatespendingintent;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.location.Location;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
/**
* Utility methods used in this sample.
*/
class Utils {
final static String KEY_LOCATION_UPDATES_REQUESTED = "location-updates-requested";
final static String KEY_LOCATION_UPDATES_RESULT = "location-update-result";
static void setRequestingLocationUpdates(Context context, boolean value) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putBoolean(KEY_LOCATION_UPDATES_REQUESTED, value)
.apply();
}
static boolean getRequestingLocationUpdates(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(KEY_LOCATION_UPDATES_REQUESTED, false);
}
/**
* Posts a notification in the notification bar when a transition is detected.
* If the user clicks the notification, control goes to the MainActivity.
*/
static void sendNotification(Context context, String notificationDetails) {
// Create an explicit content Intent that starts the main Activity.
Intent notificationIntent = new Intent(context, MainActivity.class);
notificationIntent.putExtra("from_notification", true);
// Construct a task stack.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
// Add the main Activity to the task stack as the parent.
stackBuilder.addParentStack(MainActivity.class);
// Push the content Intent onto the stack.
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack.
PendingIntent notificationPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
// Define the notification settings.
builder.setSmallIcon(R.mipmap.ic_launcher)
// In a real app, you may want to use a library like Volley
// to decode the Bitmap.
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(),
R.mipmap.ic_launcher))
.setColor(Color.RED)
.setContentTitle("Location update")
.setContentText(notificationDetails)
.setContentIntent(notificationPendingIntent);
// Dismiss notification once the user touches it.
builder.setAutoCancel(true);
// Get an instance of the Notification manager
NotificationManager mNotificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}
/**
* Returns the title for reporting about a list of {@link Location} objects.
*
* @param context The {@link Context}.
*/
static String getLocationResultTitle(Context context, List<Location> locations) {
String numLocationsReported = context.getResources().getQuantityString(
R.plurals.num_locations_reported, locations.size(), locations.size());
return numLocationsReported + ": " + DateFormat.getDateTimeInstance().format(new Date());
}
/**
* Returns te text for reporting about a list of {@link Location} objects.
*
* @param locations List of {@link Location}s.
*/
private static String getLocationResultText(Context context, List<Location> locations) {
if (locations.isEmpty()) {
return context.getString(R.string.unknown_location);
}
StringBuilder sb = new StringBuilder();
for (Location location : locations) {
sb.append("(");
sb.append(location.getLatitude());
sb.append(", ");
sb.append(location.getLongitude());
sb.append(")");
sb.append("\n");
}
return sb.toString();
}
static void setLocationUpdatesResult(Context context, List<Location> locations) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putString(KEY_LOCATION_UPDATES_RESULT, getLocationResultTitle(context, locations)
+ "\n" + getLocationResultText(context, locations))
.apply();
}
static String getLocationUpdatesResult(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getString(KEY_LOCATION_UPDATES_RESULT, "");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment