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 kuzki.net.exercisetracker; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; import android.content.SharedPreferences; import android.graphics.Color; import android.location.Geocoder; import android.location.Location; import android.os.Build; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesClient; import com.google.android.gms.common.GooglePlayServicesUtil; 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.GoogleMap; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.LatLng; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Random; /** * This the app's main Activity. It provides buttons for requesting the various features of the * app, displays the current location, the current address, and the status of the location client * and updating services. * * {@link #getLocation} gets the current location using the Location Services getLastLocation() * function. {@link #getAddress} calls geocoding to get a street address for the current location. * {@link #startUpdates} sends a request to Location Services to send periodic location updates to * the Activity. * {@link #stopUpdates} cancels previous periodic update requests. * * The update interval is hard-coded to be 5 seconds. */ public class MainActivity extends FragmentActivity implements LocationListener, GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener { private static final boolean DEBUG = false; private static final boolean FAKE_LOC = true; private static final String TAG = MainActivity.class.getCanonicalName(); // A request to connect to Location Services private LocationRequest mLocationRequest; // Stores the current instantiation of the location client in this object private LocationClient mLocationClient; // Handles to UI widgets private TextView mLatLng; private TextView mConnectionState; private TextView mConnectionStatus; private Button mStartStopRecording; // Handle to SharedPreferences for this app SharedPreferences mPrefs; // Handle to a SharedPreferences editor SharedPreferences.Editor mEditor; /* * Note if updates have been turned on. Starts out as "false"; is set to "true" in the * method handleRequestSuccess of LocationUpdateReceiver. * */ boolean mUpdatesRequested = false; GoogleMap mMap; private LatLng mPrevLatLng; private RouteDatabaseHelper mDbHelper; private List<TimedDistance> tDistance = null; private List<TimedLocation> tLocation = null; private List<TimedDistance> base = null; private Route mRecordRoute = null; private Route mBaseRoute = null; public final static String EXTRA_ROUTE_NAME = "net.kuzki.excercisetracker.ROUTE_NAME"; private boolean mStartPressed = false; private boolean mIsRecordingMode = false; /* * Initialize the Activity */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Get handles to the UI view objects mLatLng = (TextView) findViewById(R.id.lat_lng); mConnectionState = (TextView) findViewById(R.id.text_connection_state); mConnectionStatus = (TextView) findViewById(R.id.text_connection_status); if (!DEBUG) { mLatLng.setVisibility(View.GONE); mConnectionState.setVisibility(View.GONE); mConnectionStatus.setVisibility(View.GONE); } mStartStopRecording = (Button) findViewById(R.id.start_stop_button); // Create a new global location parameters object mLocationRequest = LocationRequest.create(); /* * Set the update interval */ mLocationRequest.setInterval(kuzki.net.exercisetracker.LocationUtils.UPDATE_INTERVAL_IN_MILLISECONDS); // Use high accuracy mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); // Set the interval ceiling to one minute mLocationRequest .setFastestInterval(kuzki.net.exercisetracker.LocationUtils.FAST_INTERVAL_CEILING_IN_MILLISECONDS); // Note that location updates are off until the user turns them on mUpdatesRequested = false; // Open Shared Preferences mPrefs = getSharedPreferences(kuzki.net.exercisetracker.LocationUtils.SHARED_PREFERENCES, Context.MODE_PRIVATE); // Get an editor mEditor = mPrefs.edit(); /* * Create a new location client, using the enclosing class to * handle callbacks. */ mLocationClient = new LocationClient(this, this, this); mDbHelper = new RouteDatabaseHelper(this); setUpMapIfNeeded(); tDistance = new ArrayList<TimedDistance>(); tLocation = new ArrayList<TimedLocation>(); base = new ArrayList<TimedDistance>(); // Draw a dummy blank diagram. tDistance.add(new TimedDistance(0L, 0.0)); base = tDistance; DrawUtil.drawChart(tDistance, base, (LinearLayout) findViewById(R.id.diagram_layout), MainActivity.this); parseIntent(); } private void parseIntent() { Intent intent = getIntent(); if (intent.hasExtra(EXTRA_ROUTE_NAME)) { String routeName = intent.getStringExtra(EXTRA_ROUTE_NAME); Log.d(TAG, "Opened with existing route intent: " + routeName); Route route = mDbHelper.getRoute(routeName); if (route != null) { Log.d(TAG, "Setting & drawing base route " + route.name); mBaseRoute = route; MapUtil.moveCameraToRoute(mMap, mBaseRoute); MapUtil.drawRoute(mMap, null, mBaseRoute); DrawUtil.drawChart(mBaseRoute, mBaseRoute, (LinearLayout) findViewById(R.id.diagram_layout), MainActivity.this); } } else { mIsRecordingMode = true; } } private void setUpMapIfNeeded() { // Do a null check to confirm that we have not already instantiated the map. if (mMap == null) { // Try to obtain the map from the SupportMapFragment. mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap(); // Check if we were successful in obtaining the map. if (mMap != null) { MapUtil.setupMapFragment(mMap); } } } /* * Called when the Activity is no longer visible at all. * Stop updates and disconnect. */ @Override public void onStop() { // If the client is connected if (mLocationClient.isConnected()) { stopPeriodicUpdates(); } // After disconnect() is called, the client is considered "dead". mLocationClient.disconnect(); super.onStop(); } /* * Called when the Activity is going into the background. * Parts of the UI may be visible, but the Activity is inactive. */ @Override public void onPause() { // Save the current setting for updates mEditor.putBoolean(kuzki.net.exercisetracker.LocationUtils.KEY_UPDATES_REQUESTED, mUpdatesRequested); mEditor.commit(); super.onPause(); stopUpdates(); } /* * Called when the Activity is restarted, even before it becomes visible. */ @Override public void onStart() { super.onStart(); /* * Connect the client. Don't re-start any requests here; * instead, wait for onResume() */ mLocationClient.connect(); } /* * Called when the system detects that this Activity is now visible. */ @Override public void onResume() { super.onResume(); // If the app already has a setting for getting location updates, get it if (mPrefs.contains(kuzki.net.exercisetracker.LocationUtils.KEY_UPDATES_REQUESTED)) { mUpdatesRequested = mPrefs.getBoolean(kuzki.net.exercisetracker.LocationUtils.KEY_UPDATES_REQUESTED, false); // Otherwise, turn off location updates until requested } else { mEditor.putBoolean(kuzki.net.exercisetracker.LocationUtils.KEY_UPDATES_REQUESTED, false); mEditor.commit(); } } /* * Handle results returned to this Activity by other Activities started with * startActivityForResult(). In particular, the method onConnectionFailed() in * LocationUpdateRemover and LocationUpdateRequester may call startResolutionForResult() to * start an Activity that handles Google Play services problems. The result of this * call returns here, to onActivityResult. */ @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 kuzki.net.exercisetracker.LocationUtils.CONNECTION_FAILURE_RESOLUTION_REQUEST: switch (resultCode) { // If Google Play services resolved the problem case Activity.RESULT_OK: // Log the result Log.d(kuzki.net.exercisetracker.LocationUtils.APPTAG, getString(R.string.resolved)); // Display the result mConnectionState.setText(R.string.connected); mConnectionStatus.setText(R.string.resolved); break; // If any other result was returned by Google Play services default: // Log the result Log.d(kuzki.net.exercisetracker.LocationUtils.APPTAG, getString(R.string.no_resolution)); // Display the result mConnectionState.setText(R.string.disconnected); mConnectionStatus.setText(R.string.no_resolution); break; } // If any other request code was received default: // Report that this Activity received an unknown requestCode Log.d(kuzki.net.exercisetracker.LocationUtils.APPTAG, getString(R.string.unknown_activity_request_code, requestCode)); break; } } /** * 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(kuzki.net.exercisetracker.LocationUtils.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(), kuzki.net.exercisetracker.LocationUtils.APPTAG); } return false; } } /** * Invoked by the "Get Location" button. * * Calls getLastLocation() to get the current location * * @param v The view object associated with this method, in this case a Button. */ public void getLocation(View v) { // If Google Play Services is available if (servicesConnected()) { // Get the current location Location currentLocation = mLocationClient.getLastLocation(); // Display the current location in the UI mLatLng.setText(kuzki.net.exercisetracker.LocationUtils.getLatLng(this, currentLocation)); } } /** * Invoked by the "Get Address" button. * Get the address of the current location, using reverse geocoding. This only works if * a geocoding service is available. * * @param v The view object associated with this method, in this case a Button. */ // For Eclipse with ADT, suppress warnings about Geocoder.isPresent() @SuppressLint("NewApi") public void getAddress(View v) { // In Gingerbread and later, use Geocoder.isPresent() to see if a geocoder is available. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && !Geocoder.isPresent()) { // No geocoder is present. Issue an error message Toast.makeText(this, R.string.no_geocoder_available, Toast.LENGTH_LONG).show(); return; } if (servicesConnected()) { // Get the current location Location currentLocation = mLocationClient.getLastLocation(); } } /** * Invoked by the "Start Updates" button * Sends a request to start location updates */ private void startUpdates() { mUpdatesRequested = true; if (servicesConnected()) { startPeriodicUpdates(); } } /** * Invoked by the "Stop Updates" button * Sends a request to remove location updates * request them. */ private void stopUpdates() { mUpdatesRequested = false; if (servicesConnected()) { stopPeriodicUpdates(); } } /* * Called by Location Services when the request to connect the * client finishes successfully. At this point, you can * request the current location or start periodic updates */ @Override public void onConnected(Bundle bundle) { mConnectionStatus.setText(R.string.connected); mLocationClient.setMockMode(FAKE_LOC); if (FAKE_LOC) { mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() { @Override public void onMapClick(LatLng position) { Log.d(TAG, "Setting mock location to: " + position); Location newLoc = createLocation(position); mLocationClient.setMockLocation(newLoc); onLocationChanged(newLoc); } }); Location defaultLoc = createDefaultLocation(); MapUtil.moveCameraToLocation(mMap, defaultLoc.getLatitude(), defaultLoc.getLongitude()); } Location currentLocation = mLocationClient.getLastLocation(); if (currentLocation != null) { MapUtil.moveCameraToLocation(mMap, currentLocation.getLatitude(), currentLocation.getLongitude()); } if (mUpdatesRequested) { startPeriodicUpdates(); } } private static final String PROVIDER = "flp"; private static final double LAT = 37.377166; private static final double LNG = -122.086966; private static final float ACCURACY = 3.0f; private Location createDefaultLocation() { return createLocation(new LatLng(LAT, LNG)); } private Location createLocation(LatLng pos) { Location newLocation = new Location(PROVIDER); newLocation.setLatitude(pos.latitude); newLocation.setLongitude(pos.longitude); newLocation.setAccuracy(ACCURACY); Calendar c = Calendar.getInstance(); int seconds = c.get(Calendar.SECOND); newLocation.setTime(seconds * 1000); return newLocation; } /* * Called by Location Services if the connection to the * location client drops because of an error. */ @Override public void onDisconnected() { mConnectionStatus.setText(R.string.disconnected); } /* * Called by Location Services if the attempt to * Location Services fails. */ @Override public void onConnectionFailed(ConnectionResult connectionResult) { /* * 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()) { try { // Start an Activity that tries to resolve the error connectionResult.startResolutionForResult(this, kuzki.net.exercisetracker.LocationUtils.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. showErrorDialog(connectionResult.getErrorCode()); } } /** * Report location updates to the UI. * * @param location The updated location. */ @Override public void onLocationChanged(Location location) { Log.d(TAG, "onLocationChanged: " + location.getLongitude() + location.getLatitude()); // Report to the UI that the location was updated mConnectionStatus.setText(R.string.location_updated); if (FAKE_LOC) { // updateFakeLocation(location); } // In the UI, set the latitude and longitude to the value received mLatLng.setText(kuzki.net.exercisetracker.LocationUtils.getLatLng(this, location)); if (mRecordRoute != null) { mRecordRoute.addPoint(location.getLatitude(), location.getLongitude(), location.getTime()); updateFragmentsWithRoute(mRecordRoute, mBaseRoute); } } private void updateFragmentsWithRoute(Route route, Route baseRoute) { MapUtil.drawRoute(mMap, route, baseRoute); MapUtil.moveCameraToRoute(mMap, route); if (route != null) { DrawUtil.drawChart(route, baseRoute, (LinearLayout) findViewById(R.id.diagram_layout), MainActivity.this); } else { DrawUtil.drawChart(route, route, (LinearLayout) findViewById(R.id.diagram_layout), MainActivity.this); } } private void updateFakeLocation(Location location) { if (mPrevLatLng == null) { mPrevLatLng = new LatLng(location.getLatitude(), location.getLongitude()); } Random r = new Random(); double side1 = 2.5 * r.nextDouble() - 1 > 0 ? -1 : 1; double side2 = 2.5 * r.nextDouble() - 1 > 0 ? -1 : 1; double dist1 = (double) ((int) (r.nextDouble() * 100000)) * .000000001; double dist2 = (double) ((int) (r.nextDouble() * 100000)) * .000000001; location.setLatitude(mPrevLatLng.latitude + dist1 * side1); location.setLongitude(mPrevLatLng.longitude + dist2 * side2); mPrevLatLng = new LatLng(location.getLatitude(), location.getLongitude()); } /** * In response to a request to start updates, send a request * to Location Services */ private void startPeriodicUpdates() { mLocationClient.requestLocationUpdates(mLocationRequest, this); mConnectionState.setText(R.string.location_requested); } /** * In response to a request to stop updates, send a request to * Location Services */ private void stopPeriodicUpdates() { mLocationClient.removeLocationUpdates(this); mConnectionState.setText(R.string.location_updates_stopped); } /** * Show a dialog returned by Google Play services for the * connection error code * * @param errorCode An error code returned from onConnectionFailed */ private void showErrorDialog(int errorCode) { // Get the error dialog from Google Play services Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(errorCode, this, kuzki.net.exercisetracker.LocationUtils.CONNECTION_FAILURE_RESOLUTION_REQUEST); // If Google Play services can provide an error dialog if (errorDialog != null) { // Create a new DialogFragment in which to show the error dialog ErrorDialogFragment errorFragment = new ErrorDialogFragment(); // Set the dialog in the DialogFragment errorFragment.setDialog(errorDialog); // Show the error dialog in the DialogFragment errorFragment.show(getSupportFragmentManager(), kuzki.net.exercisetracker.LocationUtils.APPTAG); } } public void onStartStopRecordingClicked(View view) { if (mStartPressed) { // Stop clicked. stopUpdates(); mStartStopRecording.setText(R.string.start); mStartStopRecording.setBackgroundColor(Color.GREEN); if (mIsRecordingMode) { this.getRouteName(); } else { Toast.makeText(this, "Next time try harder!", Toast.LENGTH_SHORT).show(); } } else { // Start clicked. startUpdates(); mStartStopRecording.setText(R.string.stop); mStartStopRecording.setBackgroundColor(Color.RED); if (mIsRecordingMode) { mRecordRoute = new Route(); } else { Toast.makeText(this, "Go! You can do it!", Toast.LENGTH_SHORT).show(); } } mStartPressed = !mStartPressed; } private void addRecordedRouteToDb() { mDbHelper.addRoute(mRecordRoute); } private void getRouteName() { AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.setTitle(R.string.name_your_route_prompt); // Set an EditText view to get user input final EditText input = new EditText(this); alert.setView(input); alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mRecordRoute.name = input.getText().toString(); addRecordedRouteToDb(); finish(); } }); alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mRecordRoute = null; mMap.clear(); } }); alert.show(); } public static Intent createNewRouteIntent(Context context) { Intent intent = new Intent(context, MainActivity.class); return intent; } public static Intent createExistingRouteIntent(Context context, String routeName) { Intent intent = new Intent(context, MainActivity.class); intent.putExtra(EXTRA_ROUTE_NAME, routeName); return intent; } /** * 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; } } }