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.s