Home | History | Annotate | Download | only in com.example.android.wearable.speedtracker
      1 /*
      2  * Copyright (C) 2014 Google Inc. All Rights Reserved.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.wearable.speedtracker;
     18 
     19 import com.google.android.gms.common.ConnectionResult;
     20 import com.google.android.gms.common.api.GoogleApiClient;
     21 import com.google.android.gms.common.api.ResultCallback;
     22 import com.google.android.gms.common.api.Status;
     23 import com.google.android.gms.location.LocationListener;
     24 import com.google.android.gms.location.LocationRequest;
     25 import com.google.android.gms.location.LocationServices;
     26 import com.google.android.gms.wearable.DataApi;
     27 import com.google.android.gms.wearable.PutDataMapRequest;
     28 import com.google.android.gms.wearable.PutDataRequest;
     29 import com.google.android.gms.wearable.Wearable;
     30 
     31 import android.app.Activity;
     32 import android.app.AlertDialog;
     33 import android.content.DialogInterface;
     34 import android.content.Intent;
     35 import android.content.SharedPreferences;
     36 import android.content.pm.PackageManager;
     37 import android.location.Location;
     38 import android.os.Bundle;
     39 import android.os.Handler;
     40 import android.preference.PreferenceManager;
     41 import android.util.Log;
     42 import android.view.View;
     43 import android.view.WindowManager;
     44 import android.widget.ImageButton;
     45 import android.widget.ImageView;
     46 import android.widget.TextView;
     47 
     48 import com.example.android.wearable.speedtracker.common.Constants;
     49 import com.example.android.wearable.speedtracker.common.LocationEntry;
     50 import com.example.android.wearable.speedtracker.ui.LocationSettingActivity;
     51 
     52 import java.util.Calendar;
     53 
     54 /**
     55  * The main activity for the wearable app. User can pick a speed limit, and after this activity
     56  * obtains a fix on the GPS, it starts reporting the speed. In addition to showing the current
     57  * speed, if user's speed gets close to the selected speed limit, the color of speed turns yellow
     58  * and if the user exceeds the speed limit, it will turn red. In order to show the user that GPS
     59  * location data is coming in, a small green dot keeps on blinking while GPS data is available.
     60  */
     61 public class WearableMainActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
     62         GoogleApiClient.OnConnectionFailedListener, LocationListener {
     63 
     64     private static final String TAG = "WearableActivity";
     65 
     66     private static final long UPDATE_INTERVAL_MS = 5 * 1000;
     67     private static final long FASTEST_INTERVAL_MS = 5 * 1000;
     68 
     69     public static final float MPH_IN_METERS_PER_SECOND = 2.23694f;
     70 
     71     public static final String PREFS_SPEED_LIMIT_KEY = "speed_limit";
     72     public static final int SPEED_LIMIT_DEFAULT_MPH = 45;
     73     private static final long INDICATOR_DOT_FADE_AWAY_MS = 500L;
     74 
     75     private GoogleApiClient mGoogleApiClient;
     76     private TextView mSpeedLimitText;
     77     private TextView mCurrentSpeedText;
     78     private ImageView mSaveImageView;
     79     private TextView mAcquiringGps;
     80     private TextView mCurrentSpeedMphText;
     81 
     82     private int mCurrentSpeedLimit;
     83     private float mCurrentSpeed;
     84     private View mDot;
     85     private Handler mHandler = new Handler();
     86     private Calendar mCalendar;
     87     private boolean mSaveGpsLocation;
     88 
     89     private enum SpeedState {
     90         BELOW(R.color.speed_below), CLOSE(R.color.speed_close), ABOVE(R.color.speed_above);
     91 
     92         private int mColor;
     93 
     94         SpeedState(int color) {
     95             mColor = color;
     96         }
     97 
     98         int getColor() {
     99             return mColor;
    100         }
    101     }
    102 
    103     @Override
    104     protected void onCreate(Bundle savedInstanceState) {
    105         super.onCreate(savedInstanceState);
    106 
    107         setContentView(R.layout.main_activity);
    108         if (!hasGps()) {
    109             // If this hardware doesn't support GPS, we prefer to exit.
    110             // Note that when such device is connected to a phone with GPS capabilities, the
    111             // framework automatically routes the location requests to the phone. For this
    112             // application, this would not be desirable so we exit the app but for some other
    113             // applications, that might be a valid scenario.
    114             Log.w(TAG, "This hardware doesn't have GPS, so we exit");
    115             new AlertDialog.Builder(this)
    116                     .setMessage(getString(R.string.gps_not_available))
    117                     .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
    118                         @Override
    119                         public void onClick(DialogInterface dialog, int id) {
    120                             finish();
    121                             dialog.cancel();
    122                         }
    123                     })
    124                     .setOnDismissListener(new DialogInterface.OnDismissListener() {
    125                         @Override
    126                         public void onDismiss(DialogInterface dialog) {
    127                             dialog.cancel();
    128                             finish();
    129                         }
    130                     })
    131                     .setCancelable(false)
    132                     .create()
    133                     .show();
    134         }
    135 
    136         setupViews();
    137         updateSpeedVisibility(false);
    138         setSpeedLimit();
    139         mGoogleApiClient = new GoogleApiClient.Builder(this)
    140                 .addApi(LocationServices.API)
    141                 .addApi(Wearable.API)
    142                 .addConnectionCallbacks(this)
    143                 .addOnConnectionFailedListener(this)
    144                 .build();
    145         mGoogleApiClient.connect();
    146     }
    147 
    148     private void setupViews() {
    149         mSpeedLimitText = (TextView) findViewById(R.id.max_speed_text);
    150         mCurrentSpeedText = (TextView) findViewById(R.id.current_speed_text);
    151         mSaveImageView = (ImageView) findViewById(R.id.saving);
    152         ImageButton settingButton = (ImageButton) findViewById(R.id.settings);
    153         mAcquiringGps = (TextView) findViewById(R.id.acquiring_gps);
    154         mCurrentSpeedMphText = (TextView) findViewById(R.id.current_speed_mph);
    155         mDot = findViewById(R.id.dot);
    156 
    157         settingButton.setOnClickListener(new View.OnClickListener() {
    158             @Override
    159             public void onClick(View v) {
    160                 Intent speedIntent = new Intent(WearableMainActivity.this,
    161                         SpeedPickerActivity.class);
    162                 startActivity(speedIntent);
    163             }
    164         });
    165 
    166         mSaveImageView.setOnClickListener(new View.OnClickListener() {
    167             @Override
    168             public void onClick(View v) {
    169                 Intent savingIntent = new Intent(WearableMainActivity.this,
    170                         LocationSettingActivity.class);
    171                 startActivity(savingIntent);
    172             }
    173         });
    174     }
    175 
    176     private void setSpeedLimit(int speedLimit) {
    177         mSpeedLimitText.setText(getString(R.string.speed_limit, speedLimit));
    178     }
    179 
    180     private void setSpeedLimit() {
    181         SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
    182         mCurrentSpeedLimit = pref.getInt(PREFS_SPEED_LIMIT_KEY, SPEED_LIMIT_DEFAULT_MPH);
    183         setSpeedLimit(mCurrentSpeedLimit);
    184     }
    185 
    186     private void setCurrentSpeed(float speed) {
    187         mCurrentSpeed = speed;
    188         mCurrentSpeedText.setText(String.format(getString(R.string.speed_format), speed));
    189         adjustColor();
    190     }
    191 
    192     /**
    193      * Adjusts the color of the speed based on its value relative to the speed limit.
    194      */
    195     private void adjustColor() {
    196         SpeedState state = SpeedState.ABOVE;
    197         if (mCurrentSpeed <= mCurrentSpeedLimit - 5) {
    198             state = SpeedState.BELOW;
    199         } else if (mCurrentSpeed <= mCurrentSpeedLimit) {
    200             state = SpeedState.CLOSE;
    201         }
    202 
    203         mCurrentSpeedText.setTextColor(getResources().getColor(state.getColor()));
    204     }
    205 
    206     @Override
    207     public void onConnected(Bundle bundle) {
    208         LocationRequest locationRequest = LocationRequest.create()
    209                 .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
    210                 .setInterval(UPDATE_INTERVAL_MS)
    211                 .setFastestInterval(FASTEST_INTERVAL_MS);
    212 
    213         LocationServices.FusedLocationApi
    214                 .requestLocationUpdates(mGoogleApiClient, locationRequest, this)
    215                 .setResultCallback(new ResultCallback<Status>() {
    216 
    217                     @Override
    218                     public void onResult(Status status) {
    219                         if (status.getStatus().isSuccess()) {
    220                             if (Log.isLoggable(TAG, Log.DEBUG)) {
    221                                 Log.d(TAG, "Successfully requested location updates");
    222                             }
    223                         } else {
    224                             Log.e(TAG,
    225                                     "Failed in requesting location updates, "
    226                                             + "status code: "
    227                                             + status.getStatusCode() + ", message: " + status
    228                                             .getStatusMessage());
    229                         }
    230                     }
    231                 });
    232     }
    233 
    234     @Override
    235     public void onConnectionSuspended(int i) {
    236         if (Log.isLoggable(TAG, Log.DEBUG)) {
    237             Log.d(TAG, "onConnectionSuspended(): connection to location client suspended");
    238         }
    239         LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
    240     }
    241 
    242     @Override
    243     public void onConnectionFailed(ConnectionResult connectionResult) {
    244         Log.e(TAG, "onConnectionFailed(): connection to location client failed");
    245     }
    246 
    247     @Override
    248     public void onLocationChanged(Location location) {
    249         updateSpeedVisibility(true);
    250         setCurrentSpeed(location.getSpeed() * MPH_IN_METERS_PER_SECOND);
    251         flashDot();
    252         addLocationEntry(location.getLatitude(), location.getLongitude());
    253     }
    254 
    255     /**
    256      * Causes the (green) dot blinks when new GPS location data is acquired.
    257      */
    258     private void flashDot() {
    259         mHandler.post(new Runnable() {
    260             @Override
    261             public void run() {
    262                 mDot.setVisibility(View.VISIBLE);
    263             }
    264         });
    265         mDot.setVisibility(View.VISIBLE);
    266         mHandler.postDelayed(new Runnable() {
    267             @Override
    268             public void run() {
    269                 mDot.setVisibility(View.INVISIBLE);
    270             }
    271         }, INDICATOR_DOT_FADE_AWAY_MS);
    272     }
    273 
    274     /**
    275      * Adjusts the visibility of speed indicator based on the arrival of GPS data.
    276      */
    277     private void updateSpeedVisibility(boolean speedVisible) {
    278         if (speedVisible) {
    279             mAcquiringGps.setVisibility(View.GONE);
    280             mCurrentSpeedText.setVisibility(View.VISIBLE);
    281             mCurrentSpeedMphText.setVisibility(View.VISIBLE);
    282         } else {
    283             mAcquiringGps.setVisibility(View.VISIBLE);
    284             mCurrentSpeedText.setVisibility(View.GONE);
    285             mCurrentSpeedMphText.setVisibility(View.GONE);
    286         }
    287     }
    288 
    289     /**
    290      * Adds a data item to the data Layer storage
    291      */
    292     private void addLocationEntry(double latitude, double longitude) {
    293         if (!mSaveGpsLocation || !mGoogleApiClient.isConnected()) {
    294             return;
    295         }
    296         mCalendar.setTimeInMillis(System.currentTimeMillis());
    297         LocationEntry entry = new LocationEntry(mCalendar, latitude, longitude);
    298         String path = Constants.PATH + "/" + mCalendar.getTimeInMillis();
    299         PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(path);
    300         putDataMapRequest.getDataMap().putDouble(Constants.KEY_LATITUDE, entry.latitude);
    301         putDataMapRequest.getDataMap().putDouble(Constants.KEY_LONGITUDE, entry.longitude);
    302         putDataMapRequest.getDataMap()
    303                 .putLong(Constants.KEY_TIME, entry.calendar.getTimeInMillis());
    304         PutDataRequest request = putDataMapRequest.asPutDataRequest();
    305         Wearable.DataApi.putDataItem(mGoogleApiClient, request)
    306                 .setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
    307                     @Override
    308                     public void onResult(DataApi.DataItemResult dataItemResult) {
    309                         if (!dataItemResult.getStatus().isSuccess()) {
    310                             Log.e(TAG, "AddPoint:onClick(): Failed to set the data, "
    311                                     + "status: " + dataItemResult.getStatus()
    312                                     .getStatusCode());
    313                         }
    314                     }
    315                 });
    316     }
    317 
    318     @Override
    319     protected void onStop() {
    320         super.onStop();
    321         if (mGoogleApiClient.isConnected()) {
    322             LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
    323         }
    324         mGoogleApiClient.disconnect();
    325     }
    326 
    327     @Override
    328     protected void onResume() {
    329         super.onResume();
    330         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    331         mCalendar = Calendar.getInstance();
    332         setSpeedLimit();
    333         adjustColor();
    334         updateRecordingIcon();
    335     }
    336 
    337     private void updateRecordingIcon() {
    338         mSaveGpsLocation = LocationSettingActivity.getGpsRecordingStatusFromPreferences(this);
    339         mSaveImageView.setImageResource(mSaveGpsLocation ? R.drawable.ic_gps_saving_grey600_96dp
    340                 : R.drawable.ic_gps_not_saving_grey600_96dp);
    341     }
    342 
    343     /**
    344      * Returns {@code true} if this device has the GPS capabilities.
    345      */
    346     private boolean hasGps() {
    347         return getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS);
    348     }
    349 }
    350