• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SpeedState(int color)94         SpeedState(int color) {
95             mColor = color;
96         }
97 
getColor()98         int getColor() {
99             return mColor;
100         }
101     }
102 
103     @Override
onCreate(Bundle savedInstanceState)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 
setupViews()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 
setSpeedLimit(int speedLimit)176     private void setSpeedLimit(int speedLimit) {
177         mSpeedLimitText.setText(getString(R.string.speed_limit, speedLimit));
178     }
179 
setSpeedLimit()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 
setCurrentSpeed(float speed)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      */
adjustColor()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
onConnected(Bundle bundle)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
onConnectionSuspended(int i)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
onConnectionFailed(ConnectionResult connectionResult)243     public void onConnectionFailed(ConnectionResult connectionResult) {
244         Log.e(TAG, "onConnectionFailed(): connection to location client failed");
245     }
246 
247     @Override
onLocationChanged(Location location)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      */
flashDot()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      */
updateSpeedVisibility(boolean speedVisible)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      */
addLocationEntry(double latitude, double longitude)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
onStop()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
onResume()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 
updateRecordingIcon()337     private void updateRecordingIcon() {
338         mSaveGpsLocation = LocationSettingActivity.getGpsRecordingStatusFromPreferences(this);
339         mSaveImageView.setImageResource(mSaveGpsLocation ? R.drawable.ic_file_download_googblue_24dp
340                 : R.drawable.ic_file_download_grey600_24dp);
341     }
342 
343     /**
344      * Returns {@code true} if this device has the GPS capabilities.
345      */
hasGps()346     private boolean hasGps() {
347         return getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS);
348     }
349 }
350