blob: 9610f227b1f5ca36028cf08a0a3b57c624649665 [file] [log] [blame]
/*
* Copyright (C) 2014 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.example.android.wearable.speedtracker;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
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.wearable.DataApi;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import com.example.android.wearable.speedtracker.common.Constants;
import com.example.android.wearable.speedtracker.common.LocationEntry;
import com.example.android.wearable.speedtracker.ui.LocationSettingActivity;
import java.util.Calendar;
/**
* The main activity for the wearable app. User can pick a speed limit, and after this activity
* obtains a fix on the GPS, it starts reporting the speed. In addition to showing the current
* speed, if user's speed gets close to the selected speed limit, the color of speed turns yellow
* and if the user exceeds the speed limit, it will turn red. In order to show the user that GPS
* location data is coming in, a small green dot keeps on blinking while GPS data is available.
*/
public class WearableMainActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener, LocationListener {
private static final String TAG = "WearableActivity";
private static final long UPDATE_INTERVAL_MS = 5 * 1000;
private static final long FASTEST_INTERVAL_MS = 5 * 1000;
public static final float MPH_IN_METERS_PER_SECOND = 2.23694f;
public static final String PREFS_SPEED_LIMIT_KEY = "speed_limit";
public static final int SPEED_LIMIT_DEFAULT_MPH = 45;
private static final long INDICATOR_DOT_FADE_AWAY_MS = 500L;
private GoogleApiClient mGoogleApiClient;
private TextView mSpeedLimitText;
private TextView mCurrentSpeedText;
private ImageView mSaveImageView;
private TextView mAcquiringGps;
private TextView mCurrentSpeedMphText;
private int mCurrentSpeedLimit;
private float mCurrentSpeed;
private View mDot;
private Handler mHandler = new Handler();
private Calendar mCalendar;
private boolean mSaveGpsLocation;
private enum SpeedState {
BELOW(R.color.speed_below), CLOSE(R.color.speed_close), ABOVE(R.color.speed_above);
private int mColor;
SpeedState(int color) {
mColor = color;
}
int getColor() {
return mColor;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
if (!hasGps()) {
// If this hardware doesn't support GPS, we prefer to exit.
// Note that when such device is connected to a phone with GPS capabilities, the
// framework automatically routes the location requests to the phone. For this
// application, this would not be desirable so we exit the app but for some other
// applications, that might be a valid scenario.
Log.w(TAG, "This hardware doesn't have GPS, so we exit");
new AlertDialog.Builder(this)
.setMessage(getString(R.string.gps_not_available))
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
finish();
dialog.cancel();
}
})
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
dialog.cancel();
finish();
}
})
.setCancelable(false)
.create()
.show();
}
setupViews();
updateSpeedVisibility(false);
setSpeedLimit();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
mGoogleApiClient.connect();
}
private void setupViews() {
mSpeedLimitText = (TextView) findViewById(R.id.max_speed_text);
mCurrentSpeedText = (TextView) findViewById(R.id.current_speed_text);
mSaveImageView = (ImageView) findViewById(R.id.saving);
ImageButton settingButton = (ImageButton) findViewById(R.id.settings);
mAcquiringGps = (TextView) findViewById(R.id.acquiring_gps);
mCurrentSpeedMphText = (TextView) findViewById(R.id.current_speed_mph);
mDot = findViewById(R.id.dot);
settingButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent speedIntent = new Intent(WearableMainActivity.this,
SpeedPickerActivity.class);
startActivity(speedIntent);
}
});
mSaveImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent savingIntent = new Intent(WearableMainActivity.this,
LocationSettingActivity.class);
startActivity(savingIntent);
}
});
}
private void setSpeedLimit(int speedLimit) {
mSpeedLimitText.setText(getString(R.string.speed_limit, speedLimit));
}
private void setSpeedLimit() {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
mCurrentSpeedLimit = pref.getInt(PREFS_SPEED_LIMIT_KEY, SPEED_LIMIT_DEFAULT_MPH);
setSpeedLimit(mCurrentSpeedLimit);
}
private void setCurrentSpeed(float speed) {
mCurrentSpeed = speed;
mCurrentSpeedText.setText(String.format(getString(R.string.speed_format), speed));
adjustColor();
}
/**
* Adjusts the color of the speed based on its value relative to the speed limit.
*/
private void adjustColor() {
SpeedState state = SpeedState.ABOVE;
if (mCurrentSpeed <= mCurrentSpeedLimit - 5) {
state = SpeedState.BELOW;
} else if (mCurrentSpeed <= mCurrentSpeedLimit) {
state = SpeedState.CLOSE;
}
mCurrentSpeedText.setTextColor(getResources().getColor(state.getColor()));
}
@Override
public void onConnected(Bundle bundle) {
LocationRequest locationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(UPDATE_INTERVAL_MS)
.setFastestInterval(FASTEST_INTERVAL_MS);
LocationServices.FusedLocationApi
.requestLocationUpdates(mGoogleApiClient, locationRequest, this)
.setResultCallback(new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
if (status.getStatus().isSuccess()) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Successfully requested location updates");
}
} else {
Log.e(TAG,
"Failed in requesting location updates, "
+ "status code: "
+ status.getStatusCode() + ", message: " + status
.getStatusMessage());
}
}
});
}
@Override
public void onConnectionSuspended(int i) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConnectionSuspended(): connection to location client suspended");
}
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e(TAG, "onConnectionFailed(): connection to location client failed");
}
@Override
public void onLocationChanged(Location location) {
updateSpeedVisibility(true);
setCurrentSpeed(location.getSpeed() * MPH_IN_METERS_PER_SECOND);
flashDot();
addLocationEntry(location.getLatitude(), location.getLongitude());
}
/**
* Causes the (green) dot blinks when new GPS location data is acquired.
*/
private void flashDot() {
mHandler.post(new Runnable() {
@Override
public void run() {
mDot.setVisibility(View.VISIBLE);
}
});
mDot.setVisibility(View.VISIBLE);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mDot.setVisibility(View.INVISIBLE);
}
}, INDICATOR_DOT_FADE_AWAY_MS);
}
/**
* Adjusts the visibility of speed indicator based on the arrival of GPS data.
*/
private void updateSpeedVisibility(boolean speedVisible) {
if (speedVisible) {
mAcquiringGps.setVisibility(View.GONE);
mCurrentSpeedText.setVisibility(View.VISIBLE);
mCurrentSpeedMphText.setVisibility(View.VISIBLE);
} else {
mAcquiringGps.setVisibility(View.VISIBLE);
mCurrentSpeedText.setVisibility(View.GONE);
mCurrentSpeedMphText.setVisibility(View.GONE);
}
}
/**
* Adds a data item to the data Layer storage
*/
private void addLocationEntry(double latitude, double longitude) {
if (!mSaveGpsLocation || !mGoogleApiClient.isConnected()) {
return;
}
mCalendar.setTimeInMillis(System.currentTimeMillis());
LocationEntry entry = new LocationEntry(mCalendar, latitude, longitude);
String path = Constants.PATH + "/" + mCalendar.getTimeInMillis();
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(path);
putDataMapRequest.getDataMap().putDouble(Constants.KEY_LATITUDE, entry.latitude);
putDataMapRequest.getDataMap().putDouble(Constants.KEY_LONGITUDE, entry.longitude);
putDataMapRequest.getDataMap()
.putLong(Constants.KEY_TIME, entry.calendar.getTimeInMillis());
PutDataRequest request = putDataMapRequest.asPutDataRequest();
Wearable.DataApi.putDataItem(mGoogleApiClient, request)
.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
@Override
public void onResult(DataApi.DataItemResult dataItemResult) {
if (!dataItemResult.getStatus().isSuccess()) {
Log.e(TAG, "AddPoint:onClick(): Failed to set the data, "
+ "status: " + dataItemResult.getStatus()
.getStatusCode());
}
}
});
}
@Override
protected void onStop() {
super.onStop();
if (mGoogleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}
mGoogleApiClient.disconnect();
}
@Override
protected void onResume() {
super.onResume();
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mCalendar = Calendar.getInstance();
setSpeedLimit();
adjustColor();
updateRecordingIcon();
}
private void updateRecordingIcon() {
mSaveGpsLocation = LocationSettingActivity.getGpsRecordingStatusFromPreferences(this);
mSaveImageView.setImageResource(mSaveGpsLocation ? R.drawable.ic_file_download_googblue_24dp
: R.drawable.ic_file_download_grey600_24dp);
}
/**
* Returns {@code true} if this device has the GPS capabilities.
*/
private boolean hasGps() {
return getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS);
}
}