Java tutorial
/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.hoen.geofencing; import java.util.ArrayList; import java.util.List; import me.hoen.geofencing.GeofenceUtils.REMOVE_TYPE; import me.hoen.geofencing.GeofenceUtils.REQUEST_TYPE; import me.hoen.geofencing.display.EventListActivity; import android.app.Activity; import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Color; import android.location.Location; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.widget.Toast; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks; import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.location.Geofence; import com.google.android.gms.location.LocationClient; import com.google.android.gms.location.LocationListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.MapFragment; import com.google.android.gms.maps.model.CircleOptions; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; public class MainActivity extends FragmentActivity implements LocationListener, ConnectionCallbacks, OnConnectionFailedListener { protected LocationRequest mLocationRequest; protected LocationClient mLocationClient; // Milliseconds per second public static final int MILLISECONDS_PER_SECOND = 1000; // The update interval public static final int UPDATE_INTERVAL_IN_SECONDS = 60; // A fast interval ceiling public static final int FAST_CEILING_IN_SECONDS = 1; // Update interval in milliseconds public static final long UPDATE_INTERVAL_IN_MILLISECONDS = MILLISECONDS_PER_SECOND * UPDATE_INTERVAL_IN_SECONDS; // A fast ceiling of update intervals, used when the app is visible public static final long FAST_INTERVAL_CEILING_IN_MILLISECONDS = MILLISECONDS_PER_SECOND * FAST_CEILING_IN_SECONDS; /* * Use to set an expiration time for a geofence. After this amount of time * Location Services will stop tracking the geofence. Remember to unregister * a geofence when you're finished with it. Otherwise, your app will use up * battery. To continue monitoring a geofence indefinitely, set the * expiration time to Geofence#NEVER_EXPIRE. */ private static final long GEOFENCE_EXPIRATION_IN_HOURS = 12; public static final long GEOFENCE_EXPIRATION_IN_MILLISECONDS = GEOFENCE_EXPIRATION_IN_HOURS * DateUtils.HOUR_IN_MILLIS; // Store the current request private REQUEST_TYPE mRequestType; // Store the current type of removal private REMOVE_TYPE mRemoveType; // Persistent storage for geofences private SimpleGeofenceStore mPrefs; // Store a list of geofences to add List<Geofence> mCurrentGeofences; // Add geofences handler private GeofenceRequester mGeofenceRequester; // Remove geofences handler private GeofenceRemover mGeofenceRemover; /* * An instance of an inner class that receives broadcasts from listeners and * from the IntentService that receives geofence transition events */ private GeofenceSampleReceiver mBroadcastReceiver; // An intent filter for the broadcast receiver private IntentFilter mIntentFilter; // Store the list of geofences to remove private List<String> mGeofenceIdsToRemove; protected GoogleMap map; protected Marker marker; protected ArrayList<SimpleGeofence> simpleGeofences; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); simpleGeofences = SimpleGeofenceDataStore.getInstance().getSimpleGeofences(); // Create a new broadcast receiver to receive updates from the listeners // and service mBroadcastReceiver = new GeofenceSampleReceiver(); // Create an intent filter for the broadcast receiver mIntentFilter = new IntentFilter(); // Action for broadcast Intents that report successful addition of // geofences mIntentFilter.addAction(GeofenceUtils.ACTION_GEOFENCES_ADDED); // Action for broadcast Intents that report successful removal of // geofences mIntentFilter.addAction(GeofenceUtils.ACTION_GEOFENCES_REMOVED); // Action for broadcast Intents containing various types of geofencing // errors mIntentFilter.addAction(GeofenceUtils.ACTION_GEOFENCE_ERROR); // All Location Services sample apps use this category mIntentFilter.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES); // Instantiate a new geofence storage area mPrefs = new SimpleGeofenceStore(this); // Instantiate the current List of geofences mCurrentGeofences = new ArrayList<Geofence>(); // Instantiate a Geofence requester mGeofenceRequester = new GeofenceRequester(this); // Instantiate a Geofence remover mGeofenceRemover = new GeofenceRemover(this); // Attach to the main UI setContentView(R.layout.activity_main); map = ((MapFragment) getFragmentManager().findFragmentById(R.id.map)).getMap(); map.setMyLocationEnabled(true); registerGeofences(); mLocationRequest = LocationRequest.create(); mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); mLocationRequest.setFastestInterval(FAST_INTERVAL_CEILING_IN_MILLISECONDS); mLocationClient = new LocationClient(this, this, this); mLocationClient.connect(); } /* * Handle results returned to this Activity by other Activities started with * startActivityForResult(). In particular, the method onConnectionFailed() * in GeofenceRemover and GeofenceRequester may call * startResolutionForResult() to start an Activity that handles Google Play * services problems. The result of this call returns here, to * onActivityResult. calls */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { // Choose what to do based on the request code switch (requestCode) { // If the request code matches the code sent in onConnectionFailed case GeofenceUtils.CONNECTION_FAILURE_RESOLUTION_REQUEST: switch (resultCode) { // If Google Play services resolved the problem case Activity.RESULT_OK: // If the request was to add geofences if (GeofenceUtils.REQUEST_TYPE.ADD == mRequestType) { // Toggle the request flag and send a new request mGeofenceRequester.setInProgressFlag(false); // Restart the process of adding the current geofences mGeofenceRequester.addGeofences(mCurrentGeofences); // If the request was to remove geofences } else if (GeofenceUtils.REQUEST_TYPE.REMOVE == mRequestType) { // Toggle the removal flag and send a new removal request mGeofenceRemover.setInProgressFlag(false); // If the removal was by Intent if (GeofenceUtils.REMOVE_TYPE.INTENT == mRemoveType) { // Restart the removal of all geofences for the // PendingIntent mGeofenceRemover.removeGeofencesByIntent(mGeofenceRequester.getRequestPendingIntent()); // If the removal was by a List of geofence IDs } else { // Restart the removal of the geofence list mGeofenceRemover.removeGeofencesById(mGeofenceIdsToRemove); } } break; // If any other result was returned by Google Play services default: // Report that Google Play services was unable to resolve the // problem. Log.d(GeofenceUtils.APPTAG, getString(R.string.no_resolution)); } // If any other request code was received default: // Report that this Activity received an unknown requestCode Log.d(GeofenceUtils.APPTAG, getString(R.string.unknown_activity_request_code, requestCode)); break; } } /* * Whenever the Activity resumes, reconnect the client to Location Services * and reload the last geofences that were set */ @Override protected void onResume() { super.onResume(); // Register the broadcast receiver to receive status updates LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, mIntentFilter); /* * Get existing geofences from the latitude, longitude, and radius * values stored in SharedPreferences. If no values exist, null is * returned. */ for (SimpleGeofence sg : this.simpleGeofences) { SimpleGeofence mUIGeofence = mPrefs.getGeofence(sg.getId()); if (mUIGeofence != null) { CircleOptions circleOptions1 = new CircleOptions() .center(new LatLng(mUIGeofence.getLatitude(), mUIGeofence.getLongitude())) .radius(mUIGeofence.getRadius()).strokeColor(Color.GRAY).strokeWidth(2) .fillColor(Color.RED); map.addCircle(circleOptions1); } } } /* * Save the current geofence settings in SharedPreferences. */ @Override protected void onPause() { super.onPause(); for (SimpleGeofence sg : this.simpleGeofences) { mPrefs.setGeofence(sg.getId(), sg); } } /** * Verify that Google Play services is available before making a request. * * @return true if Google Play services is available, otherwise false */ 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) { // In debug mode, log the status Log.d(GeofenceUtils.APPTAG, getString(R.string.play_services_available)); // Continue return true; // Google Play services was not available for some reason } else { // Display an error dialog Dialog dialog = GooglePlayServicesUtil.getErrorDialog(resultCode, this, 0); if (dialog != null) { ErrorDialogFragment errorFragment = new ErrorDialogFragment(); errorFragment.setDialog(dialog); errorFragment.show(getSupportFragmentManager(), GeofenceUtils.APPTAG); } return false; } } /** * Called when the user clicks the "Register geofences" button. Get the * geofence parameters for each geofence and add them to a List. Create the * PendingIntent containing an Intent that Location Services sends to this * app's broadcast receiver when Location Services detects a geofence * transition. Send the List and the PendingIntent to Location Services. */ public void registerGeofences() { /* * Record the request as an ADD. If a connection error occurs, the app * can automatically restart the add request if Google Play services can * fix the error */ mRequestType = GeofenceUtils.REQUEST_TYPE.ADD; /* * Check for Google Play services. Do this after setting the request * type. If connecting to Google Play services fails, onActivityResult * is eventually called, and it needs to know what type of request was * in progress. */ if (!servicesConnected()) { return; } for (SimpleGeofence sg : this.simpleGeofences) { mPrefs.setGeofence(sg.getId(), sg); } for (SimpleGeofence sg : this.simpleGeofences) { /* * Add Geofence objects to a List. toGeofence() creates a Location * Services Geofence object from a flat object */ mCurrentGeofences.add(sg.toGeofence()); } // Start the request. Fail if there's already a request in progress try { // Try to add geofences mGeofenceRequester.addGeofences(mCurrentGeofences); } catch (UnsupportedOperationException e) { // Notify user that previous request hasn't finished. Toast.makeText(this, R.string.add_geofences_already_requested_error, Toast.LENGTH_LONG).show(); } } @Override public void onDestroy() { mLocationClient.disconnect(); super.onDestroy(); } @Override public void onConnectionFailed(ConnectionResult arg0) { } @Override public void onConnected(Bundle connectionHint) { startPeriodicUpdates(); } @Override public void onDisconnected() { stopPeriodicUpdates(); } @Override public void onLocationChanged(Location location) { if (location.hasAccuracy() && location.getAccuracy() < 100) { LatLng itemPosition = new LatLng(location.getLatitude(), location.getLongitude()); if (marker == null) { MarkerOptions markerOptions = new MarkerOptions().position(itemPosition).title("My coordinates"); marker = map.addMarker(markerOptions); map.moveCamera(CameraUpdateFactory.newLatLngZoom(itemPosition, 17)); } else { marker.setPosition(itemPosition); } } } protected void startPeriodicUpdates() { mLocationClient.requestLocationUpdates(mLocationRequest, this); } protected void stopPeriodicUpdates() { mLocationClient.removeLocationUpdates(this); } /** * Define a Broadcast receiver that receives updates from connection * listeners and the geofence transition service. */ public class GeofenceSampleReceiver extends BroadcastReceiver { /* * Define the required method for broadcast receivers This method is * invoked when a broadcast Intent triggers the receiver */ @Override public void onReceive(Context context, Intent intent) { // Check the action code and determine what to do String action = intent.getAction(); // Intent contains information about errors in adding or removing // geofences if (TextUtils.equals(action, GeofenceUtils.ACTION_GEOFENCE_ERROR)) { handleGeofenceError(context, intent); // Intent contains information about successful addition or // removal of geofences } else if (TextUtils.equals(action, GeofenceUtils.ACTION_GEOFENCES_ADDED) || TextUtils.equals(action, GeofenceUtils.ACTION_GEOFENCES_REMOVED)) { handleGeofenceStatus(context, intent); // Intent contains information about a geofence transition } else if (TextUtils.equals(action, GeofenceUtils.ACTION_GEOFENCE_TRANSITION)) { handleGeofenceTransition(context, intent); // The Intent contained an invalid action } else { Log.e(GeofenceUtils.APPTAG, getString(R.string.invalid_action_detail, action)); Toast.makeText(context, R.string.invalid_action, Toast.LENGTH_LONG).show(); } } /** * If you want to display a UI message about adding or removing * geofences, put it here. * * @param context * A Context for this component * @param intent * The received broadcast Intent */ private void handleGeofenceStatus(Context context, Intent intent) { } /** * Report geofence transitions to the UI * * @param context * A Context for this component * @param intent * The Intent containing the transition */ private void handleGeofenceTransition(Context context, Intent intent) { /* * If you want to change the UI when a transition occurs, put the * code here. The current design of the app uses a notification to * inform the user that a transition has occurred. */ } /** * Report addition or removal errors to the UI, using a Toast * * @param intent * A broadcast Intent sent by ReceiveTransitionsIntentService */ private void handleGeofenceError(Context context, Intent intent) { String msg = intent.getStringExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS); Log.e(GeofenceUtils.APPTAG, msg); Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); } } /** * Define a DialogFragment to display the error dialog generated in * showErrorDialog. */ public static class ErrorDialogFragment extends DialogFragment { // Global field to contain the error dialog private Dialog mDialog; /** * Default constructor. Sets the dialog field to null */ public ErrorDialogFragment() { super(); mDialog = null; } /** * Set the dialog to display * * @param dialog * An error dialog */ public void setDialog(Dialog dialog) { mDialog = dialog; } /* * This method must return a Dialog to the DialogFragment. */ @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return mDialog; } } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main_menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.list: Intent i = new Intent(getApplicationContext(), EventListActivity.class); startActivity(i); return true; default: return super.onOptionsItemSelected(item); } } }