Java tutorial
/** * Copyright 2014 Google Inc. All Rights Reserved. * <p/> * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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.googlemapapp; import android.app.PendingIntent; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Color; import android.location.Location; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.Geofence; import com.google.android.gms.location.GeofencingApi; import com.google.android.gms.location.GeofencingRequest; import com.google.android.gms.location.LocationListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.CircleOptions; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Map; /** * Demonstrates how to create and remove geofences using the GeofencingApi. Uses an IntentService * to monitor geofence transitions and creates notifications whenever a device enters or exits * a geofence. * <p/> * This sample requires a device's Location settings to be turned on. It also requires * the ACCESS_FINE_LOCATION permission, as specified in AndroidManifest.xml. * <p/> * Note that this Activity implements ResultCallback<Status>, requiring that * {@code onResult} must be defined. The {@code onResult} runs when the result of calling * {@link GeofencingApi#addGeofences(GoogleApiClient, GeofencingRequest, PendingIntent)} addGeofences()} or * {@link GeofencingApi#removeGeofences(GoogleApiClient, java.util.List)} removeGeofences()} * becomes available. */ public class MainActivity extends AppCompatActivity implements ConnectionCallbacks, OnConnectionFailedListener, ResultCallback<Status>, OnMapReadyCallback, LocationListener { /** * The desired interval for location updates. Inexact. Updates may be more or less frequent. */ public static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000; /** * The fastest rate for active location updates. Exact. Updates will never be more frequent * than this value. */ public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2; protected static final String TAG = "MainActivity"; // Keys for storing activity state in the Bundle. protected final static String REQUESTING_LOCATION_UPDATES_KEY = "requesting-location-updates-key"; protected final static String LOCATION_KEY = "location-key"; protected final static String LAST_UPDATED_TIME_STRING_KEY = "last-updated-time-string-key"; /** * Provides the entry point to Google Play services. */ protected GoogleApiClient mGoogleApiClient; /** * The list of geofences used in this sample. */ protected ArrayList<Geofence> mGeofenceList; /** * Stores parameters for requests to the FusedLocationProviderApi. */ protected LocationRequest mLocationRequest; /** * Represents a geographical location. */ protected Location mCurrentLocation; /** * Tracks the status of the location updates request. Value changes when the user presses the * Start Updates and Stop Updates buttons. */ protected Boolean mRequestingLocationUpdates; /** * Time when the location was updated represented as a String. */ protected String mLastUpdateTime; /** * Used to keep track of whether geofences were added. */ private boolean mGeofencesAdded; /** * Used when requesting to add or remove geofences. */ private PendingIntent mGeofencePendingIntent; /** * Used to persist application state about whether geofences were added. */ private SharedPreferences mSharedPreferences; // Buttons for kicking off the process of adding or removing geofences. private Button mAddGeofencesButton; private Button mRemoveGeofencesButton; private GoogleMap mMap; private boolean isMovedCamera; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); // Get the UI widgets. mAddGeofencesButton = (Button) findViewById(R.id.add_geofences_button); mRemoveGeofencesButton = (Button) findViewById(R.id.remove_geofences_button); // Obtain the SupportMapFragment and get notified when the map is ready to be used. SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this); // Empty list for storing geofences. mGeofenceList = new ArrayList<Geofence>(); // Initially set the PendingIntent used in addGeofences() and removeGeofences() to null. mGeofencePendingIntent = null; // Retrieve an instance of the SharedPreferences object. mSharedPreferences = getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, MODE_PRIVATE); // Get the value of mGeofencesAdded from SharedPreferences. Set to false as a default. mGeofencesAdded = mSharedPreferences.getBoolean(Constants.GEOFENCES_ADDED_KEY, false); setButtonsEnabledState(); // // Get the geofences used. Geofence data is hard coded in this sample. // populateGeofenceList(); mRequestingLocationUpdates = mGeofencesAdded; mLastUpdateTime = ""; // Update values using data stored in the Bundle. updateValuesFromBundle(savedInstanceState); // Kick off the request to build GoogleApiClient. buildGoogleApiClient(); } /** * Updates fields based on data stored in the bundle. * * @param savedInstanceState The activity state saved in the Bundle. */ private void updateValuesFromBundle(Bundle savedInstanceState) { Log.i(TAG, "Updating values from bundle"); if (savedInstanceState != null) { // Update the value of mRequestingLocationUpdates from the Bundle, and make sure that // the Start Updates and Stop Updates buttons are correctly enabled or disabled. if (savedInstanceState.keySet().contains(REQUESTING_LOCATION_UPDATES_KEY)) { mRequestingLocationUpdates = savedInstanceState.getBoolean(REQUESTING_LOCATION_UPDATES_KEY); setButtonsEnabledState(); } // Update the value of mCurrentLocation from the Bundle and update the UI to show the // correct latitude and longitude. if (savedInstanceState.keySet().contains(LOCATION_KEY)) { // Since LOCATION_KEY was found in the Bundle, we can be sure that mCurrentLocation // is not null. mCurrentLocation = savedInstanceState.getParcelable(LOCATION_KEY); } // Update the value of mLastUpdateTime from the Bundle and update the UI. if (savedInstanceState.keySet().contains(LAST_UPDATED_TIME_STRING_KEY)) { mLastUpdateTime = savedInstanceState.getString(LAST_UPDATED_TIME_STRING_KEY); } // updateUI(); } } /** * Builds a GoogleApiClient. Uses the {@code #addApi} method to request the LocationServices API. */ protected synchronized void buildGoogleApiClient() { mGoogleApiClient = new GoogleApiClient.Builder(this).addConnectionCallbacks(this) .addOnConnectionFailedListener(this).addApi(LocationServices.API).build(); createLocationRequest(); } /** * 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. */ protected 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. mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS); // 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_IN_MILLISECONDS); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); } /** * Handles the Start Updates button and requests start of location updates. Does nothing if * updates have already been requested. */ public void startUpdatesButtonHandler(View view) { if (!mRequestingLocationUpdates) { mRequestingLocationUpdates = true; setButtonsEnabledState(); startLocationUpdates(); } } /** * Handles the Stop Updates button, and requests removal of location updates. Does nothing if * updates were not previously requested. */ public void stopUpdatesButtonHandler(View view) { if (mRequestingLocationUpdates) { mRequestingLocationUpdates = false; setButtonsEnabledState(); stopLocationUpdates(); } } /** * Requests location updates from the FusedLocationApi. */ protected void startLocationUpdates() { // The final argument to {@code requestLocationUpdates()} is a LocationListener // (http://developer.android.com/reference/com/google/android/gms/location/LocationListener.html). if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.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(mGoogleApiClient, mLocationRequest, this); } /** * Removes location updates from the FusedLocationApi. */ protected void stopLocationUpdates() { // It is a good practice to remove location requests when the activity is in a paused or // stopped state. Doing so helps battery performance and is especially // recommended in applications that request frequent location updates. // The final argument to {@code requestLocationUpdates()} is a LocationListener // (http://developer.android.com/reference/com/google/android/gms/location/LocationListener.html). LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this); } @Override protected void onStart() { super.onStart(); mGoogleApiClient.connect(); } @Override public void onResume() { super.onResume(); // Within {@code onPause()}, we pause location updates, but leave the // connection to GoogleApiClient intact. Here, we resume receiving // location updates if the user has requested them. if (mGoogleApiClient.isConnected() && mRequestingLocationUpdates) { startLocationUpdates(); } } @Override protected void onPause() { super.onPause(); // Stop location updates to save battery, but don't disconnect the GoogleApiClient object. if (mGoogleApiClient.isConnected()) { stopLocationUpdates(); } } @Override protected void onStop() { super.onStop(); mGoogleApiClient.disconnect(); } /** * Runs when a GoogleApiClient object successfully connects. */ @Override public void onConnected(Bundle connectionHint) { Log.i(TAG, "Connected to GoogleApiClient"); // If the initial location was never previously requested, we use // FusedLocationApi.getLastLocation() to get it. If it was previously requested, we store // its value in the Bundle and check for it in onCreate(). We // do not request it again unless the user specifically requests location updates by pressing // the Start Updates button. // // Because we cache the value of the initial location in the Bundle, it means that if the // user launches the activity, // moves to a new location, and then changes the device orientation, the original location // is displayed as the activity is re-created. if (mCurrentLocation == null) { if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.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; } mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); mLastUpdateTime = DateFormat.getTimeInstance().format(new Date()); // updateUI(); } // If the user presses the Start Updates button before GoogleApiClient connects, we set // mRequestingLocationUpdates to true (see startUpdatesButtonHandler()). Here, we check // the value of mRequestingLocationUpdates and if it is true, we start location updates. if (mRequestingLocationUpdates) { startLocationUpdates(); } } /** * Callback that fires when the location changes. */ @Override public void onLocationChanged(Location location) { mCurrentLocation = location; mLastUpdateTime = DateFormat.getTimeInstance().format(new Date()); LatLng latLngCurrent = new LatLng(mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude()); if (!isMovedCamera) { isMovedCamera = true; mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLngCurrent, 10.0f)); } // updateUI(); // Toast.makeText(this, getResources().getString(R.string.location_updated_message), Toast.LENGTH_SHORT).show(); } @Override public void onConnectionFailed(ConnectionResult result) { // Refer to the javadoc for ConnectionResult to see what error codes might be returned in // onConnectionFailed. Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + result.getErrorCode()); } @Override public void onConnectionSuspended(int cause) { // The connection to Google Play services was lost for some reason. Log.i(TAG, "Connection suspended"); mGoogleApiClient.connect(); // onConnected() will be called again automatically when the service reconnects } /** * Builds and returns a GeofencingRequest. Specifies the list of geofences to be monitored. * Also specifies how the geofence notifications are initially triggered. */ private GeofencingRequest getGeofencingRequest() { GeofencingRequest.Builder builder = new GeofencingRequest.Builder(); // The INITIAL_TRIGGER_ENTER flag indicates that geofencing service should trigger a // GEOFENCE_TRANSITION_ENTER notification when the geofence is added and if the device // is already inside that geofence. builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER); // Add the geofences to be monitored by geofencing service. builder.addGeofences(mGeofenceList); // Return a GeofencingRequest. return builder.build(); } /** * Adds geofences, which sets alerts to be notified when the device enters or exits one of the * specified geofences. Handles the success or failure results returned by addGeofences(). */ public void addGeofencesButtonHandler(View view) { if (!mGoogleApiClient.isConnected()) { Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show(); return; } try { LocationServices.GeofencingApi.addGeofences(mGoogleApiClient, // The GeofenceRequest object. getGeofencingRequest(), // A pending intent that that is reused when calling removeGeofences(). This // pending intent is used to generate an intent when a matched geofence // transition is observed. getGeofencePendingIntent()).setResultCallback(this); // Result processed in onResult(). } catch (SecurityException securityException) { // Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission. logSecurityException(securityException); } } /** * Removes geofences, which stops further notifications when the device enters or exits * previously registered geofences. */ public void removeGeofencesButtonHandler(View view) { if (!mGoogleApiClient.isConnected()) { Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show(); return; } try { // Remove geofences. LocationServices.GeofencingApi.removeGeofences(mGoogleApiClient, // This is the same pending intent that was used in addGeofences(). getGeofencePendingIntent()).setResultCallback(this); // Result processed in onResult(). } catch (SecurityException securityException) { // Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission. logSecurityException(securityException); } } private void logSecurityException(SecurityException securityException) { Log.e(TAG, "Invalid location permission. " + "You need to use ACCESS_FINE_LOCATION with geofences", securityException); } /** * Runs when the result of calling addGeofences() and removeGeofences() becomes available. * Either method can complete successfully or with an error. * <p/> * Since this activity implements the {@link ResultCallback} interface, we are required to * define this method. * * @param status The Status returned through a PendingIntent when addGeofences() or * removeGeofences() get called. */ public void onResult(Status status) { if (status.isSuccess()) { // Update state and save in shared preferences. mGeofencesAdded = !mGeofencesAdded; SharedPreferences.Editor editor = mSharedPreferences.edit(); editor.putBoolean(Constants.GEOFENCES_ADDED_KEY, mGeofencesAdded); editor.apply(); // Update the UI. Adding geofences enables the Remove Geofences button, and removing // geofences enables the Add Geofences button. setButtonsEnabledState(); Toast.makeText(this, getString(mGeofencesAdded ? R.string.geofences_added : R.string.geofences_removed), Toast.LENGTH_SHORT).show(); } else { // Get the status code for the error and log it using a user-friendly message. String errorMessage = GeofenceErrorMessages.getErrorString(this, status.getStatusCode()); Log.e(TAG, errorMessage); } } /** * Gets a PendingIntent to send with the request to add or remove Geofences. Location Services * issues the Intent inside this PendingIntent whenever a geofence transition occurs for the * current list of geofences. * * @return A PendingIntent for the IntentService that handles geofence transitions. */ private PendingIntent getGeofencePendingIntent() { // Reuse the PendingIntent if we already have it. if (mGeofencePendingIntent != null) { return mGeofencePendingIntent; } Intent intent = new Intent(this, GeofenceTransitionsIntentService.class); // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling // addGeofences() and removeGeofences(). return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } /** * This sample hard codes geofence data. A real app might dynamically create geofences based on * the user's location. */ public void populateGeofenceList() { for (Map.Entry<String, LatLng> entry : Constants.BAY_AREA_LANDMARKS.entrySet()) { mGeofenceList.add(new Geofence.Builder() // Set the request ID of the geofence. This is a string to identify this // geofence. .setRequestId(entry.getKey()) // Set the circular region of this geofence. .setCircularRegion(entry.getValue().latitude, entry.getValue().longitude, Constants.GEOFENCE_RADIUS_IN_METERS) // Set the expiration duration of the geofence. This geofence gets automatically // removed after this period of time. .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS) // Set the transition types of interest. Alerts are only generated for these // transition. We track entry and exit transitions in this sample. .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT) // Create the geofence. .build()); LatLng sydney = new LatLng(entry.getValue().latitude, entry.getValue().longitude); mMap.addMarker(new MarkerOptions().position(sydney).title(entry.getKey())); mMap.addCircle(new CircleOptions().center(sydney).radius(Constants.GEOFENCE_RADIUS_IN_METERS) .strokeColor(Color.BLACK).fillColor(getResources().getColor(R.color.trnsprnt_black))); } } /** * Ensures that only one button is enabled at any time. The Add Geofences button is enabled * if the user hasn't yet added geofences. The Remove Geofences button is enabled if the * user has added geofences. */ private void setButtonsEnabledState() { if (mGeofencesAdded) { mAddGeofencesButton.setEnabled(false); mRemoveGeofencesButton.setEnabled(true); } else { mAddGeofencesButton.setEnabled(true); mRemoveGeofencesButton.setEnabled(false); } } @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.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; } mMap.setMyLocationEnabled(true); // Get the geofences used. Geofence data is hard coded in this sample. populateGeofenceList(); // // Add a marker in Sydney and move the camera // mMap.setOnMapLongClickListener(new GoogleMap.OnMapLongClickListener() { // @Override // public void onMapLongClick(LatLng latLng) { // // LatLng sydney = new LatLng(latLng.latitude, latLng.longitude); // mMap.addMarker(new MarkerOptions().position(sydney).title("Tapped Location")); // mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney)); // } // }); } /** * Stores activity data in the Bundle. */ public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putBoolean(REQUESTING_LOCATION_UPDATES_KEY, mRequestingLocationUpdates); savedInstanceState.putParcelable(LOCATION_KEY, mCurrentLocation); savedInstanceState.putString(LAST_UPDATED_TIME_STRING_KEY, mLastUpdateTime); super.onSaveInstanceState(savedInstanceState); } }