Synced to developers/samples/android commit bc036ecdf44cd03163c206096172299f3940b057. Change-Id: Ib68230d79ca300e7db906aff2ebfc2cb6c6968f7
350 lines
14 KiB
Java
350 lines
14 KiB
Java
/*
|
|
* 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_gps_saving_grey600_96dp
|
|
: R.drawable.ic_gps_not_saving_grey600_96dp);
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if this device has the GPS capabilities.
|
|
*/
|
|
private boolean hasGps() {
|
|
return getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS);
|
|
}
|
|
}
|