close
The Wayback Machine - https://web.archive.org/web/20171110073645/https://developer.android.com/samples/SpeedTracker/Wearable/src/com.example.android.wearable.speedtracker/WearableMainActivity.html
Skip to content

Most visited

Recently visited

navigation
SpeedTracker / Wearable / src / com.example.android.wearable.speedtracker /

WearableMainActivity.java

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.Manifest;
32
import android.annotation.SuppressLint;
33
import android.app.AlertDialog;
34
import android.content.DialogInterface;
35
import android.content.Intent;
36
import android.content.SharedPreferences;
37
import android.content.pm.PackageManager;
38
import android.location.Location;
39
import android.os.Bundle;
40
import android.os.Handler;
41
import android.preference.PreferenceManager;
42
import android.support.annotation.NonNull;
43
import android.support.v4.app.ActivityCompat;
44
import android.support.wearable.activity.WearableActivity;
45
import android.util.Log;
46
import android.view.View;
47
import android.widget.ImageView;
48
import android.widget.TextView;
49
 
50
import com.example.android.wearable.speedtracker.common.Constants;
51
import com.example.android.wearable.speedtracker.common.LocationEntry;
52
 
53
import java.util.Calendar;
54
import java.util.concurrent.TimeUnit;
55
 
56
/**
57
 * The main activity for the wearable app. User can pick a speed limit, and after this activity
58
 * obtains a fix on the GPS, it starts reporting the speed. In addition to showing the current
59
 * speed, if user's speed gets close to the selected speed limit, the color of speed turns yellow
60
 * and if the user exceeds the speed limit, it will turn red. In order to show the user that GPS
61
 * location data is coming in, a small green dot keeps on blinking while GPS data is available.
62
 */
63
public class WearableMainActivity extends WearableActivity implements
64
        GoogleApiClient.ConnectionCallbacks,
65
        GoogleApiClient.OnConnectionFailedListener,
66
        ActivityCompat.OnRequestPermissionsResultCallback,
67
        LocationListener {
68
 
69
    private static final String TAG = "WearableActivity";
70
 
71
    private static final long UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5);
72
    private static final long FASTEST_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5);
73
 
74
    private static final float MPH_IN_METERS_PER_SECOND = 2.23694f;
75
 
76
    private static final int SPEED_LIMIT_DEFAULT_MPH = 45;
77
 
78
    private static final long INDICATOR_DOT_FADE_AWAY_MS = 500L;
79
 
80
    // Request codes for changing speed limit and location permissions.
81
    private static final int REQUEST_PICK_SPEED_LIMIT = 0;
82
 
83
    // Id to identify Location permission request.
84
    private static final int REQUEST_GPS_PERMISSION = 1;
85
 
86
    // Shared Preferences for saving speed limit and location permission between app launches.
87
    private static final String PREFS_SPEED_LIMIT_KEY = "SpeedLimit";
88
 
89
    private Calendar mCalendar;
90
 
91
    private TextView mSpeedLimitTextView;
92
    private TextView mSpeedTextView;
93
    private ImageView mGpsPermissionImageView;
94
    private TextView mCurrentSpeedMphTextView;
95
    private TextView mGpsIssueTextView;
96
    private View mBlinkingGpsStatusDotView;
97
 
98
    private String mGpsPermissionNeededMessage;
99
    private String mAcquiringGpsMessage;
100
 
101
    private int mSpeedLimit;
102
    private float mSpeed;
103
 
104
    private boolean mGpsPermissionApproved;
105
 
106
    private boolean mWaitingForGpsSignal;
107
 
108
    private GoogleApiClient mGoogleApiClient;
109
 
110
    private Handler mHandler = new Handler();
111
 
112
    private enum SpeedState {
113
        BELOW(R.color.speed_below), CLOSE(R.color.speed_close), ABOVE(R.color.speed_above);
114
 
115
        private int mColor;
116
 
117
        SpeedState(int color) {
118
            mColor = color;
119
        }
120
 
121
        int getColor() {
122
            return mColor;
123
        }
124
    }
125
 
126
    @Override
127
    protected void onCreate(Bundle savedInstanceState) {
128
        super.onCreate(savedInstanceState);
129
 
130
        Log.d(TAG, "onCreate()");
131
 
132
 
133
        setContentView(R.layout.main_activity);
134
 
135
        /*
136
         * Enables Always-on, so our app doesn't shut down when the watch goes into ambient mode.
137
         * Best practice is to override onEnterAmbient(), onUpdateAmbient(), and onExitAmbient() to
138
         * optimize the display for ambient mode. However, for brevity, we aren't doing that here
139
         * to focus on learning location and permissions. For more information on best practices
140
         * in ambient mode, check this page:
141
         * https://developer.android.com/training/wearables/apps/always-on.html
142
         */
143
        setAmbientEnabled();
144
 
145
        mCalendar = Calendar.getInstance();
146
 
147
        // Enables app to handle 23+ (M+) style permissions.
148
        mGpsPermissionApproved =
149
            ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
150
                    == PackageManager.PERMISSION_GRANTED;
151
 
152
        mGpsPermissionNeededMessage = getString(R.string.permission_rationale);
153
        mAcquiringGpsMessage = getString(R.string.acquiring_gps);
154
 
155
 
156
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
157
        mSpeedLimit = sharedPreferences.getInt(PREFS_SPEED_LIMIT_KEY, SPEED_LIMIT_DEFAULT_MPH);
158
 
159
        mSpeed = 0;
160
 
161
        mWaitingForGpsSignal = true;
162
 
163
 
164
        /*
165
         * If this hardware doesn't support GPS, we warn the user. Note that when such device is
166
         * connected to a phone with GPS capabilities, the framework automatically routes the
167
         * location requests from the phone. However, if the phone becomes disconnected and the
168
         * wearable doesn't support GPS, no location is recorded until the phone is reconnected.
169
         */
170
        if (!hasGps()) {
171
            Log.w(TAG, "This hardware doesn't have GPS, so we warn user.");
172
            new AlertDialog.Builder(this)
173
                    .setMessage(getString(R.string.gps_not_available))
174
                    .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
175
                        @Override
176
                        public void onClick(DialogInterface dialog, int id) {
177
                            dialog.cancel();
178
                        }
179
                    })
180
                    .setOnDismissListener(new DialogInterface.OnDismissListener() {
181
                        @Override
182
                        public void onDismiss(DialogInterface dialog) {
183
                            dialog.cancel();
184
                        }
185
                    })
186
                    .setCancelable(false)
187
                    .create()
188
                    .show();
189
        }
190
 
191
 
192
        setupViews();
193
 
194
        mGoogleApiClient = new GoogleApiClient.Builder(this)
195
                .addApi(LocationServices.API)
196
                .addApi(Wearable.API)
197
                .addConnectionCallbacks(this)
198
                .addOnConnectionFailedListener(this)
199
                .build();
200
    }
201
 
202
    @Override
203
    protected void onPause() {
204
        super.onPause();
205
        if ((mGoogleApiClient != null) && (mGoogleApiClient.isConnected()) &&
206
                (mGoogleApiClient.isConnecting())) {
207
            LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
208
            mGoogleApiClient.disconnect();
209
        }
210
 
211
    }
212
 
213
    @Override
214
    protected void onResume() {
215
        super.onResume();
216
        if (mGoogleApiClient != null) {
217
            mGoogleApiClient.connect();
218
        }
219
    }
220
 
221
    private void setupViews() {
222
        mSpeedLimitTextView = (TextView) findViewById(R.id.max_speed_text);
223
        mSpeedTextView = (TextView) findViewById(R.id.current_speed_text);
224
        mCurrentSpeedMphTextView = (TextView) findViewById(R.id.current_speed_mph);
225
 
226
        mGpsPermissionImageView = (ImageView) findViewById(R.id.gps_permission);
227
        mGpsIssueTextView = (TextView) findViewById(R.id.gps_issue_text);
228
        mBlinkingGpsStatusDotView = findViewById(R.id.dot);
229
 
230
        updateActivityViewsBasedOnLocationPermissions();
231
    }
232
 
233
    public void onSpeedLimitClick(View view) {
234
        Intent speedIntent = new Intent(WearableMainActivity.this,
235
                SpeedPickerActivity.class);
236
        startActivityForResult(speedIntent, REQUEST_PICK_SPEED_LIMIT);
237
    }
238
 
239
    public void onGpsPermissionClick(View view) {
240
 
241
        if (!mGpsPermissionApproved) {
242
 
243
            Log.i(TAG, "Location permission has NOT been granted. Requesting permission.");
244
 
245
            // On 23+ (M+) devices, GPS permission not granted. Request permission.
246
            ActivityCompat.requestPermissions(
247
                    this,
248
                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
249
                    REQUEST_GPS_PERMISSION);
250
        }
251
    }
252
 
253
    /**
254
     * Adjusts the visibility of views based on location permissions.
255
     */
256
    private void updateActivityViewsBasedOnLocationPermissions() {
257
 
258
        /*
259
         * If the user has approved location but we don't have a signal yet, we let the user know
260
         * we are waiting on the GPS signal (this sometimes takes a little while). Otherwise, the
261
         * user might think something is wrong.
262
         */
263
        if (mGpsPermissionApproved && mWaitingForGpsSignal) {
264
 
265
            // We are getting a GPS signal w/ user permission.
266
            mGpsIssueTextView.setText(mAcquiringGpsMessage);
267
            mGpsIssueTextView.setVisibility(View.VISIBLE);
268
            mGpsPermissionImageView.setImageResource(R.drawable.ic_gps_saving_grey600_96dp);
269
 
270
            mSpeedTextView.setVisibility(View.GONE);
271
            mSpeedLimitTextView.setVisibility(View.GONE);
272
            mCurrentSpeedMphTextView.setVisibility(View.GONE);
273
 
274
        } else if (mGpsPermissionApproved) {
275
 
276
            mGpsIssueTextView.setVisibility(View.GONE);
277
 
278
            mSpeedTextView.setVisibility(View.VISIBLE);
279
            mSpeedLimitTextView.setVisibility(View.VISIBLE);
280
            mCurrentSpeedMphTextView.setVisibility(View.VISIBLE);
281
            mGpsPermissionImageView.setImageResource(R.drawable.ic_gps_saving_grey600_96dp);
282
 
283
        } else {
284
 
285
            // User needs to enable location for the app to work.
286
            mGpsIssueTextView.setVisibility(View.VISIBLE);
287
            mGpsIssueTextView.setText(mGpsPermissionNeededMessage);
288
            mGpsPermissionImageView.setImageResource(R.drawable.ic_gps_not_saving_grey600_96dp);
289
 
290
            mSpeedTextView.setVisibility(View.GONE);
291
            mSpeedLimitTextView.setVisibility(View.GONE);
292
            mCurrentSpeedMphTextView.setVisibility(View.GONE);
293
        }
294
    }
295
 
296
    private void updateSpeedInViews() {
297
 
298
        if (mGpsPermissionApproved) {
299
 
300
            mSpeedLimitTextView.setText(getString(R.string.speed_limit, mSpeedLimit));
301
            mSpeedTextView.setText(String.format(getString(R.string.speed_format), mSpeed));
302
 
303
            // Adjusts the color of the speed based on its value relative to the speed limit.
304
            SpeedState state = SpeedState.ABOVE;
305
            if (mSpeed <= mSpeedLimit - 5) {
306
                state = SpeedState.BELOW;
307
            } else if (mSpeed <= mSpeedLimit) {
308
                state = SpeedState.CLOSE;
309
            }
310
 
311
            mSpeedTextView.setTextColor(getResources().getColor(state.getColor()));
312
 
313
            // Causes the (green) dot blinks when new GPS location data is acquired.
314
            mHandler.post(new Runnable() {
315
                @Override
316
                public void run() {
317
                    mBlinkingGpsStatusDotView.setVisibility(View.VISIBLE);
318
                }
319
            });
320
            mBlinkingGpsStatusDotView.setVisibility(View.VISIBLE);
321
            mHandler.postDelayed(new Runnable() {
322
                @Override
323
                public void run() {
324
                    mBlinkingGpsStatusDotView.setVisibility(View.INVISIBLE);
325
                }
326
            }, INDICATOR_DOT_FADE_AWAY_MS);
327
        }
328
    }
329
 
330
    @SuppressLint("MissingPermission")
331
    @Override
332
    public void onConnected(Bundle bundle) {
333
 
334
        Log.d(TAG, "onConnected()");
335
        requestLocation();
336
 
337
 
338
    }
339
 
340
    private void requestLocation() {
341
        Log.d(TAG, "requestLocation()");
342
 
343
        /*
344
         * mGpsPermissionApproved covers 23+ (M+) style permissions. If that is already approved or
345
         * the device is pre-23, the app uses mSaveGpsLocation to save the user's location
346
         * preference.
347
         */
348
        if (mGpsPermissionApproved) {
349
 
350
            LocationRequest locationRequest = LocationRequest.create()
351
                    .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
352
                    .setInterval(UPDATE_INTERVAL_MS)
353
                    .setFastestInterval(FASTEST_INTERVAL_MS);
354
 
355
            LocationServices.FusedLocationApi
356
                    .requestLocationUpdates(mGoogleApiClient, locationRequest, this)
357
                    .setResultCallback(new ResultCallback<Status>() {
358
 
359
                        @Override
360
                        public void onResult(Status status) {
361
                            if (status.getStatus().isSuccess()) {
362
                                if (Log.isLoggable(TAG, Log.DEBUG)) {
363
                                    Log.d(TAG, "Successfully requested location updates");
364
                                }
365
                            } else {
366
                                Log.e(TAG,
367
                                        "Failed in requesting location updates, "
368
                                                + "status code: "
369
                                                + status.getStatusCode() + ", message: " + status
370
                                                .getStatusMessage());
371
                            }
372
                        }
373
                    });
374
        }
375
    }
376
 
377
    @Override
378
    public void onConnectionSuspended(int i) {
379
        Log.d(TAG, "onConnectionSuspended(): connection to location client suspended");
380
 
381
        LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
382
    }
383
 
384
    @Override
385
    public void onConnectionFailed(ConnectionResult connectionResult) {
386
        Log.e(TAG, "onConnectionFailed(): " + connectionResult.getErrorMessage());
387
    }
388
 
389
    @Override
390
    public void onLocationChanged(Location location) {
391
        Log.d(TAG, "onLocationChanged() : " + location);
392
 
393
 
394
        if (mWaitingForGpsSignal) {
395
            mWaitingForGpsSignal = false;
396
            updateActivityViewsBasedOnLocationPermissions();
397
        }
398
 
399
        mSpeed = location.getSpeed() * MPH_IN_METERS_PER_SECOND;
400
        updateSpeedInViews();
401
        addLocationEntry(location.getLatitude(), location.getLongitude());
402
    }
403
 
404
    /*
405
     * Adds a data item to the data Layer storage.
406
     */
407
    private void addLocationEntry(double latitude, double longitude) {
408
        if (!mGpsPermissionApproved || !mGoogleApiClient.isConnected()) {
409
            return;
410
        }
411
        mCalendar.setTimeInMillis(System.currentTimeMillis());
412
        LocationEntry entry = new LocationEntry(mCalendar, latitude, longitude);
413
        String path = Constants.PATH + "/" + mCalendar.getTimeInMillis();
414
        PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(path);
415
        putDataMapRequest.getDataMap().putDouble(Constants.KEY_LATITUDE, entry.latitude);
416
        putDataMapRequest.getDataMap().putDouble(Constants.KEY_LONGITUDE, entry.longitude);
417
        putDataMapRequest.getDataMap()
418
                .putLong(Constants.KEY_TIME, entry.calendar.getTimeInMillis());
419
        PutDataRequest request = putDataMapRequest.asPutDataRequest();
420
        request.setUrgent();
421
        Wearable.DataApi.putDataItem(mGoogleApiClient, request)
422
                .setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
423
                    @Override
424
                    public void onResult(DataApi.DataItemResult dataItemResult) {
425
                        if (!dataItemResult.getStatus().isSuccess()) {
426
                            Log.e(TAG, "AddPoint:onClick(): Failed to set the data, "
427
                                    + "status: " + dataItemResult.getStatus()
428
                                    .getStatusCode());
429
                        }
430
                    }
431
                });
432
    }
433
 
434
    /**
435
     * Handles user choices for both speed limit and location permissions (GPS tracking).
436
     */
437
    @Override
438
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
439
 
440
        if (requestCode == REQUEST_PICK_SPEED_LIMIT) {
441
            if (resultCode == RESULT_OK) {
442
                // The user updated the speed limit.
443
                int newSpeedLimit =
444
                        data.getIntExtra(SpeedPickerActivity.EXTRA_NEW_SPEED_LIMIT, mSpeedLimit);
445
 
446
                SharedPreferences sharedPreferences =
447
                        PreferenceManager.getDefaultSharedPreferences(this);
448
                SharedPreferences.Editor editor = sharedPreferences.edit();
449
                editor.putInt(WearableMainActivity.PREFS_SPEED_LIMIT_KEY, newSpeedLimit);
450
                editor.apply();
451
 
452
                mSpeedLimit = newSpeedLimit;
453
 
454
                updateSpeedInViews();
455
            }
456
        }
457
    }
458
 
459
    /**
460
     * Callback received when a permissions request has been completed.
461
     */
462
    @Override
463
    public void onRequestPermissionsResult(
464
            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
465
 
466
        Log.d(TAG, "onRequestPermissionsResult(): " + permissions);
467
 
468
 
469
        if (requestCode == REQUEST_GPS_PERMISSION) {
470
            Log.i(TAG, "Received response for GPS permission request.");
471
 
472
            if ((grantResults.length == 1)
473
                    && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
474
                Log.i(TAG, "GPS permission granted.");
475
                mGpsPermissionApproved = true;
476
 
477
                if(mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
478
                    requestLocation();
479
                }
480
 
481
            } else {
482
                Log.i(TAG, "GPS permission NOT granted.");
483
                mGpsPermissionApproved = false;
484
            }
485
 
486
            updateActivityViewsBasedOnLocationPermissions();
487
 
488
        }
489
    }
490
 
491
    /**
492
     * Returns {@code true} if this device has the GPS capabilities.
493
     */
494
    private boolean hasGps() {
495
        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS);
496
    }
497
}
This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

BERJAYA

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)