Java tutorial
/* * Copyright 2014 Randy McEoin * * 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 net.mceoin.cominghome; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender.SendIntentException; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.location.Location; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.ActionBarActivity; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.Button; 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.Geofence; import com.google.android.gms.location.LocationClient; 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.BitmapDescriptorFactory; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.Circle; 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; import net.mceoin.cominghome.geofence.FenceHandling; import net.mceoin.cominghome.geofence.GeofenceRequester; import net.mceoin.cominghome.geofence.SimpleGeofence; import net.mceoin.cominghome.geofence.SimpleGeofenceStore; import net.mceoin.cominghome.oauth.OAuthFlowApp; import net.mceoin.cominghome.structures.StructuresBean; import net.mceoin.cominghome.structures.StructuresUpdate; import net.mceoin.cominghome.structures.StructuresValues; import net.mceoin.cominghome.wizard.InitialWizardActivity; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class MainActivity extends ActionBarActivity implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener { private static final String TAG = MainActivity.class.getSimpleName(); private static final boolean debug = false; private static final String KEY_IN_RESOLUTION = "is_in_resolution"; public static final String PREFS_INITIAL_WIZARD = "initial_wizard"; public static final String PREFS_STRUCTURE_ID = "structure_id"; public static final String PREFS_STRUCTURE_NAME = "structure_name"; public static final String PREFS_LAST_AWAY_STATUS = "last_away_status"; public static final String PREFS_LAST_MAP_LATITUDE = "last_map_latitude"; public static final String PREFS_LAST_MAP_LONGITUDE = "last_map_longitude"; public static final String PREFS_NOTIFICATIONS = "notifications"; public static final String PREFS_TIME_LEFT_WORK = "time_left_work"; /** * Request code for auto Google Play Services error resolution. */ protected static final int REQUEST_CODE_RESOLUTION = 1; protected static final int REQUEST_PICK_STRUCTURE = 2; /** * Request code for the OAuth activity */ // protected static final int REQUEST_CODE_OAUTH = 2; /** * Google API client. */ // private GoogleApiClient mGoogleApiClient; /** * Determines if the client is in a resolution state, and * waiting for resolution intent to return. */ private boolean mIsInResolution; public static SharedPreferences prefs; Button connectButton; TextView structureNameText; TextView awayStatusText; Button atHomeButton; Button atWorkButton; public static String access_token = ""; public static String structure_id = ""; String structure_name = ""; String away_status = ""; long last_info_check = 0; LocationClient mLocationClient; Location mCurrentLocation; GoogleMap map; Map<String, Marker> mapMarkers = new HashMap<String, Marker>(); Map<String, Circle> mapCircles = new HashMap<String, Circle>(); public static final String FENCE_HOME = "home"; public static final String FENCE_WORK = "work"; float fenceRadius = 100; // meters private SimpleGeofenceStore mGeofenceStorage; // Store a list of geofences to add List<Geofence> mCurrentGeofences; private SimpleGeofence homeGeofence; private SimpleGeofence workGeofence; // Add geofences handler private GeofenceRequester mGeofenceRequester; private Toolbar toolbar; 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 public void setDialog(Dialog dialog) { mDialog = dialog; } // Return a Dialog to the DialogFragment. @Override @NonNull public Dialog onCreateDialog(Bundle savedInstanceState) { return mDialog; } } /** * Called when the activity is starting. Restores the activity state. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { mIsInResolution = savedInstanceState.getBoolean(KEY_IN_RESOLUTION, false); } prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); access_token = prefs.getString(OAuthFlowApp.PREF_ACCESS_TOKEN, ""); structure_id = prefs.getString(PREFS_STRUCTURE_ID, ""); Installation.id(this); setContentView(R.layout.main); connectButton = (Button) findViewById(R.id.buttonConnectNest); connectButton.setOnClickListener(new View.OnClickListener() { public void onClick(View arg0) { startActivity(new Intent().setClass(arg0.getContext(), OAuthFlowApp.class)); } }); structureNameText = (TextView) findViewById(R.id.structure_name); awayStatusText = (TextView) findViewById(R.id.away_status); playServicesConnected(); map = ((MapFragment) getFragmentManager().findFragmentById(R.id.map)).getMap(); if (map != null) { map.setMyLocationEnabled(true); float lastLatitude = prefs.getFloat(PREFS_LAST_MAP_LATITUDE, 0); float lastLongitude = prefs.getFloat(PREFS_LAST_MAP_LONGITUDE, 0); if ((lastLatitude != 0) && (lastLongitude != 0)) { LatLng current = new LatLng(lastLatitude, lastLongitude); map.moveCamera(CameraUpdateFactory.newLatLngZoom(current, 13)); } } mLocationClient = new LocationClient(this, this, this); LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, new IntentFilter(LocationService.LOCATION_CHANGED)); LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, new IntentFilter(NestUtils.GOT_INFO)); LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, new IntentFilter(NestUtils.LOST_AUTH)); atHomeButton = (Button) findViewById(R.id.buttonSetAtHome); atHomeButton.setOnClickListener(new View.OnClickListener() { public void onClick(View arg0) { homeGeofence = updateGeofenceLocation(FENCE_HOME); if (homeGeofence != null) updateGeofences(); } }); atWorkButton = (Button) findViewById(R.id.buttonSetAtWork); atWorkButton.setOnClickListener(new View.OnClickListener() { public void onClick(View arg0) { workGeofence = updateGeofenceLocation(FENCE_WORK); if (workGeofence != null) updateGeofences(); } }); // Instantiate a Geofence requester mGeofenceRequester = new GeofenceRequester(this); mGeofenceStorage = new SimpleGeofenceStore(getApplicationContext()); // Instantiate the current List of geofences mCurrentGeofences = new ArrayList<Geofence>(); loadFences(); toolbar = (Toolbar) findViewById(R.id.toolbar); if (toolbar != null) { setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } setActionBarIcon(R.drawable.home); } protected void setActionBarIcon(int iconRes) { toolbar.setNavigationIcon(iconRes); } /** * Call using isMyServiceRunning(MyService.class) * http://stackoverflow.com/questions/600207/how-to-check-if-a-service-is-running-in-android */ public static boolean isMyServiceRunning(Context context, Class<?> serviceClass) { ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { if (serviceClass.getName().equals(service.service.getClassName())) { return true; } } return false; } private void loadFences() { homeGeofence = mGeofenceStorage.getGeofence(FENCE_HOME); if (homeGeofence != null) { updateMarker(homeGeofence.getLatitude(), homeGeofence.getLongitude(), FENCE_HOME, false); mCurrentGeofences.add(homeGeofence.toGeofence()); } workGeofence = mGeofenceStorage.getGeofence(FENCE_WORK); if (workGeofence != null) { updateMarker(workGeofence.getLatitude(), workGeofence.getLongitude(), FENCE_WORK, false); mCurrentGeofences.add(workGeofence.toGeofence()); } if (!mCurrentGeofences.isEmpty()) { updateGeofences(); } } private SimpleGeofence updateGeofenceLocation(String geofenceId) { if (mLocationClient == null) { return null; } mCurrentLocation = mLocationClient.getLastLocation(); if (debug) Log.d(TAG, mCurrentLocation.toString()); if (mCurrentLocation == null) { return null; } double latitude = mCurrentLocation.getLatitude(); double longitude = mCurrentLocation.getLongitude(); SimpleGeofence newFence = new SimpleGeofence(geofenceId, latitude, longitude, fenceRadius, Geofence.NEVER_EXPIRE, Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT); SimpleGeofence oldFence = mGeofenceStorage.getGeofence(geofenceId); if (oldFence != null) { mGeofenceStorage.clearGeofence(geofenceId); } // Store this flat version mGeofenceStorage.setGeofence(geofenceId, newFence); for (Geofence fence : mCurrentGeofences) { if (fence.getRequestId().equals(geofenceId)) { mCurrentGeofences.remove(fence); } } mCurrentGeofences.add(newFence.toGeofence()); updateMarker(latitude, longitude, geofenceId, true); return newFence; } private void updateMarker(double latitude, double longitude, String geofenceId, boolean move) { LatLng current = new LatLng(latitude, longitude); Marker marker; Circle circle; if (mapMarkers.containsKey(geofenceId)) { marker = mapMarkers.get(geofenceId); marker.setPosition(current); circle = mapCircles.get(geofenceId); if (circle != null) { circle.setCenter(current); } else { Log.e(TAG, "missing circle for " + geofenceId); } } else { int iconId; if (geofenceId.equals(FENCE_HOME)) { iconId = R.drawable.home; } else { iconId = R.drawable.my_briefcase; } if (map != null) { CircleOptions circleOptions = new CircleOptions().center(current).radius(fenceRadius); // In meters circle = map.addCircle(circleOptions); mapCircles.put(geofenceId, circle); marker = map.addMarker(new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(iconId)) .anchor(0.5f, 0.5f).title(geofenceId).position(current)); mapMarkers.put(geofenceId, marker); } } if ((move) && (map != null)) map.moveCamera(CameraUpdateFactory.newLatLngZoom(current, 13)); } private void updateGeofences() { // 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(); } } private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(NestUtils.GOT_INFO)) { String got_structure_name = intent.getStringExtra("structure_name"); String got_away_status = intent.getStringExtra("away_status"); if (structureNameText != null) { structureNameText.setText(got_structure_name); } if (awayStatusText != null) { awayStatusText.setText(got_away_status); } } else if (intent.getAction().equals(NestUtils.LOST_AUTH)) { if (structureNameText != null) { structureNameText.setText(""); } if (awayStatusText != null) { awayStatusText.setText(""); } connectButton.setEnabled(true); connectButton.setVisibility(View.VISIBLE); Toast.makeText(context, R.string.lost_auth, Toast.LENGTH_LONG).show(); } else { double latitude = intent.getDoubleExtra("latitude", 0); double longitude = intent.getDoubleExtra("longitude", 0); LatLng current = new LatLng(latitude, longitude); if (debug) Log.d(TAG, "Got location update: " + latitude + ", " + longitude); if (map != null) { map.moveCamera(CameraUpdateFactory.newLatLngZoom(current, 13)); } } } }; @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main, menu); return true; } public boolean onPrepareOptionsMenu(Menu menu) { MenuItem fake_arrived = menu.findItem(R.id.fake_arrived); fake_arrived.setVisible(debug); MenuItem fake_left = menu.findItem(R.id.fake_left); fake_left.setVisible(debug); MenuItem fake_left_work = menu.findItem(R.id.fake_left_work); fake_left_work.setVisible(debug); MenuItem stop_tracking = menu.findItem(R.id.stop_tracking); stop_tracking.setVisible(false); MenuItem select_structures = menu.findItem(R.id.select_structure); int structures = StructuresUpdate.countStructureIds(getApplicationContext()); if (structures > 1) { select_structures.setVisible(true); } else { select_structures.setVisible(false); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle item selection switch (item.getItemId()) { case R.id.fake_arrived: if ((structure_id != null) && (!structure_id.isEmpty())) { FenceHandling.arrivedHome(getApplicationContext()); } return true; case R.id.fake_left: if ((structure_id != null) && (!structure_id.isEmpty())) { FenceHandling.leftHome(getApplicationContext()); } return true; case R.id.fake_left_work: if ((structure_id != null) && (!structure_id.isEmpty())) { FenceHandling.leftWork(getApplicationContext()); } return true; case R.id.stop_tracking: LocationService.sendTrackingStop(getApplicationContext()); return true; case R.id.settings: startActivity(new Intent(this, SettingsActivity.class)); return true; case R.id.select_structure: Intent pickContactIntent = new Intent(Intent.ACTION_PICK, StructuresValues.Structures.CONTENT_URI); startActivityForResult(pickContactIntent, REQUEST_PICK_STRUCTURE); return true; case R.id.history: startActivity(new Intent(this, HistoryActivity.class)); return true; case R.id.about_menu: showAbout(); return true; default: return super.onOptionsItemSelected(item); } } protected void showAbout() { // Inflate the about message contents @SuppressLint("InflateParams") View messageView = getLayoutInflater().inflate(R.layout.about, null, false); PackageInfo pi; String version = ""; try { PackageManager pm = this.getPackageManager(); String pn = this.getPackageName(); if (pm == null) { version = "unknown"; } else { pi = pm.getPackageInfo(pn, 0); version = pi.versionName; } } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } TextView versionView = (TextView) messageView.findViewById(R.id.about_version); versionView.setText(version); // When linking text, force to always use default color. This works // around a pressed color state bug. TextView textView = (TextView) messageView.findViewById(R.id.about_credits); int defaultColor = textView.getTextColors().getDefaultColor(); textView.setTextColor(defaultColor); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setIcon(R.drawable.home); builder.setTitle(R.string.app_name); builder.setView(messageView); builder.create(); builder.show(); } /** * Called when the Activity is made visible. * A connection to Play Services need to be initiated as * soon as the activity is visible. Registers {@code ConnectionCallbacks} * and {@code OnConnectionFailedListener} on the * activities itself. */ @Override protected void onStart() { super.onStart(); /* if (mGoogleApiClient == null) { mGoogleApiClient = new GoogleApiClient.Builder(this) // Optionally, add additional APIs and scopes if required. .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .build(); } mGoogleApiClient.connect(); */ mLocationClient.connect(); } @Override protected void onResume() { super.onResume(); boolean initialWizardRan = prefs.getBoolean(PREFS_INITIAL_WIZARD, false); if (!initialWizardRan) { startActivity(new Intent().setClass(getApplicationContext(), InitialWizardActivity.class)); finish(); } structure_id = prefs.getString(PREFS_STRUCTURE_ID, ""); structure_name = prefs.getString(PREFS_STRUCTURE_NAME, ""); away_status = prefs.getString(PREFS_LAST_AWAY_STATUS, ""); structureNameText.setText(structure_name); awayStatusText.setText(away_status); access_token = prefs.getString(OAuthFlowApp.PREF_ACCESS_TOKEN, ""); if (access_token.isEmpty()) { connectButton.setEnabled(true); connectButton.setVisibility(View.VISIBLE); } else { connectButton.setEnabled(false); connectButton.setVisibility(View.GONE); long currentTime = System.currentTimeMillis(); // make sure it's been at least 60 seconds since last time we got info if (currentTime > (last_info_check + 60 * 1000)) { NestUtils.getInfo(getApplicationContext(), access_token); last_info_check = System.currentTimeMillis(); } } NotificationManager mNotificationManager = (NotificationManager) this .getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.cancelAll(); /* if (structure_id.isEmpty()) { sendETAButton.setEnabled(false); } else { sendETAButton.setEnabled(true); } */ } @Override protected void onPause() { super.onPause(); if (map != null) { SharedPreferences.Editor editor = prefs.edit(); CameraPosition cameraPosition = map.getCameraPosition(); editor.putFloat(PREFS_LAST_MAP_LATITUDE, (float) cameraPosition.target.latitude); editor.putFloat(PREFS_LAST_MAP_LONGITUDE, (float) cameraPosition.target.longitude); editor.apply(); } } /** * Called when activity gets invisible. Connection to Play Services needs to * be disconnected as soon as an activity is invisible. */ @Override protected void onStop() { // if (mGoogleApiClient != null) { // mGoogleApiClient.disconnect(); // } mLocationClient.disconnect(); super.onStop(); } /** * Saves the resolution state. */ @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(KEY_IN_RESOLUTION, mIsInResolution); } /** * Handles Google Play Services resolution callbacks. */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_PICK_STRUCTURE: // Make sure the request was successful if (resultCode == RESULT_OK) { Uri structureUri = data.getData(); StructuresBean structuresBean = StructuresUpdate.getStructureId(this, structureUri); if (debug) Log.d(TAG, "structureUri=" + structureUri.toString() + " structureBean=" + structuresBean.toString()); structure_id = structuresBean.getId(); structure_name = structuresBean.getName(); away_status = structuresBean.getAway(); structureNameText.setText(structure_name); awayStatusText.setText(away_status); SharedPreferences.Editor editor = prefs.edit(); editor.putString(PREFS_STRUCTURE_ID, structure_id); editor.putString(PREFS_STRUCTURE_NAME, structure_name); editor.putString(PREFS_LAST_AWAY_STATUS, away_status); editor.apply(); } case REQUEST_CODE_RESOLUTION: retryConnecting(); break; } } private void retryConnecting() { mIsInResolution = false; // if ((mGoogleApiClient!=null) && (!mGoogleApiClient.isConnecting())) { // mGoogleApiClient.connect(); // } } /** * Called when {@code mGoogleApiClient} is connected. */ @Override public void onConnected(Bundle connectionHint) { if (debug) Log.d(TAG, "GoogleApiClient connected"); if (map != null) { // // The very first time we run, the map will be at 0,0 (or very near) // Check that we have a map and a location, if so, zoom to it // CameraPosition cameraPosition = map.getCameraPosition(); float distFrom0 = LocationService.distFrom(cameraPosition.target.latitude, cameraPosition.target.longitude, 0, 0); if (debug) Log.d(TAG, "cameraPosition lat=" + cameraPosition.target.latitude + " long=" + cameraPosition.target.longitude + " distFrom0=" + distFrom0); if (distFrom0 < 10000) { if (mLocationClient == null) { return; } mCurrentLocation = mLocationClient.getLastLocation(); if (debug) Log.d(TAG, mCurrentLocation.toString()); if (mCurrentLocation == null) { return; } double latitude = mCurrentLocation.getLatitude(); double longitude = mCurrentLocation.getLongitude(); LatLng current = new LatLng(latitude, longitude); map.animateCamera(CameraUpdateFactory.newLatLngZoom(current, 13)); } } } /** * Called when {@code mGoogleApiClient} connection is suspended. */ /* @Override public void onConnectionSuspended(int cause) { Log.i(TAG, "GoogleApiClient connection suspended"); retryConnecting(); } */ /** * Called when {@code mGoogleApiClient} is trying to connect but failed. * Handle {@code result.getResolution()} if there is a resolution * available. */ @Override public void onConnectionFailed(ConnectionResult result) { Log.i(TAG, "GoogleApiClient connection failed: " + result.toString()); if (!result.hasResolution()) { // Show a localized error dialog. GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this, 0, new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { retryConnecting(); } }).show(); return; } // If there is an existing resolution error being displayed or a resolution // activity has started before, do nothing and wait for resolution // progress to be completed. if (mIsInResolution) { return; } mIsInResolution = true; try { result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION); } catch (SendIntentException e) { Log.e(TAG, "Exception while starting resolution activity", e); retryConnecting(); } } @Override public void onDisconnected() { // Display the connection status Toast.makeText(this, "Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show(); } /** * Verify that Google Play services is available before making a request. * * @return true if Google Play services is available, otherwise false */ private boolean playServicesConnected() { // Check that Google Play services is available int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); // If Google Play services is available if (ConnectionResult.SUCCESS == resultCode) { if (debug) { int v = 0; try { v = getPackageManager().getPackageInfo("com.google.android.gms", 0).versionCode; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } Log.d(TAG, "Google Play services available: client " + GooglePlayServicesUtil.GOOGLE_PLAY_SERVICES_VERSION_CODE + " package " + v); } 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(), TAG); } return false; } } }