این وبسایت یک نسخه آزمایشی از سایت developer android می‌باشد که در حال ترجمه می‌باشد.
برای پیوستن به تیم ترجمه و خواندن مستندات اینجا کلیک کنید.
WatchFace / Wearable / src / com.example.android.wearable.watchface /

DigitalWatchFaceService.java

1
/*
2
 * Copyright (C) 2014 The Android Open Source Project
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.watchface;
18
 
19
import android.content.BroadcastReceiver;
20
import android.content.Context;
21
import android.content.Intent;
22
import android.content.IntentFilter;
23
import android.content.res.Resources;
24
import android.graphics.Canvas;
25
import android.graphics.Paint;
26
import android.graphics.Rect;
27
import android.graphics.Typeface;
28
import android.os.Bundle;
29
import android.os.Handler;
30
import android.os.Message;
31
import android.support.wearable.watchface.CanvasWatchFaceService;
32
import android.support.wearable.watchface.WatchFaceService;
33
import android.support.wearable.watchface.WatchFaceStyle;
34
import android.text.format.Time;
35
import android.util.Log;
36
import android.view.SurfaceHolder;
37
import android.view.WindowInsets;
38
 
39
import com.google.android.gms.common.ConnectionResult;
40
import com.google.android.gms.common.api.GoogleApiClient;
41
import com.google.android.gms.wearable.DataApi;
42
import com.google.android.gms.wearable.DataEvent;
43
import com.google.android.gms.wearable.DataEventBuffer;
44
import com.google.android.gms.wearable.DataItem;
45
import com.google.android.gms.wearable.DataMap;
46
import com.google.android.gms.wearable.DataMapItem;
47
import com.google.android.gms.wearable.Wearable;
48
 
49
import java.util.TimeZone;
50
import java.util.concurrent.TimeUnit;
51
 
52
/**
53
 * Sample digital watch face with blinking colons and seconds. In ambient mode, the seconds are
54
 * replaced with an AM/PM indicator and the colons don't blink. On devices with low-bit ambient
55
 * mode, the text is drawn without anti-aliasing in ambient mode. On devices which require burn-in
56
 * protection, the hours are drawn in normal rather than bold. The time is drawn with less contrast
57
 * and without seconds in mute mode.
58
 */
59
public class DigitalWatchFaceService extends CanvasWatchFaceService {
60
    private static final String TAG = "DigitalWatchFaceService";
61
 
62
    private static final Typeface BOLD_TYPEFACE =
63
            Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
64
    private static final Typeface NORMAL_TYPEFACE =
65
            Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
66
 
67
    /**
68
     * Update rate in milliseconds for normal (not ambient and not mute) mode. We update twice
69
     * a second to blink the colons.
70
     */
71
    private static final long NORMAL_UPDATE_RATE_MS = 500;
72
 
73
    /**
74
     * Update rate in milliseconds for mute mode. We update every minute, like in ambient mode.
75
     */
76
    private static final long MUTE_UPDATE_RATE_MS = TimeUnit.MINUTES.toMillis(1);
77
 
78
    @Override
79
    public Engine onCreateEngine() {
80
        return new Engine();
81
    }
82
 
83
    private class Engine extends CanvasWatchFaceService.Engine implements DataApi.DataListener,
84
            GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
85
        static final String COLON_STRING = ":";
86
 
87
        /** Alpha value for drawing time when in mute mode. */
88
        static final int MUTE_ALPHA = 100;
89
 
90
        /** Alpha value for drawing time when not in mute mode. */
91
        static final int NORMAL_ALPHA = 255;
92
 
93
        static final int MSG_UPDATE_TIME = 0;
94
 
95
        /** How often {@link #mUpdateTimeHandler} ticks in milliseconds. */
96
        long mInteractiveUpdateRateMs = NORMAL_UPDATE_RATE_MS;
97
 
98
        /** Handler to update the time periodically in interactive mode. */
99
        final Handler mUpdateTimeHandler = new Handler() {
100
            @Override
101
            public void handleMessage(Message message) {
102
                switch (message.what) {
103
                    case MSG_UPDATE_TIME:
104
                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
105
                            Log.v(TAG, "updating time");
106
                        }
107
                        invalidate();
108
                        if (shouldTimerBeRunning()) {
109
                            long timeMs = System.currentTimeMillis();
110
                            long delayMs =
111
                                    mInteractiveUpdateRateMs - (timeMs % mInteractiveUpdateRateMs);
112
                            mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
113
                        }
114
                        break;
115
                }
116
            }
117
        };
118
 
119
        GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(DigitalWatchFaceService.this)
120
                .addConnectionCallbacks(this)
121
                .addOnConnectionFailedListener(this)
122
                .addApi(Wearable.API)
123
                .build();
124
 
125
        final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
126
            @Override
127
            public void onReceive(Context context, Intent intent) {
128
                mTime.clear(intent.getStringExtra("time-zone"));
129
                mTime.setToNow();
130
            }
131
        };
132
        boolean mRegisteredTimeZoneReceiver = false;
133
 
134
        Paint mBackgroundPaint;
135
        Paint mHourPaint;
136
        Paint mMinutePaint;
137
        Paint mSecondPaint;
138
        Paint mAmPmPaint;
139
        Paint mColonPaint;
140
        float mColonWidth;
141
        boolean mMute;
142
        Time mTime;
143
        boolean mShouldDrawColons;
144
        float mXOffset;
145
        float mYOffset;
146
        String mAmString;
147
        String mPmString;
148
        int mInteractiveBackgroundColor =
149
                DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_BACKGROUND;
150
        int mInteractiveHourDigitsColor =
151
                DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_HOUR_DIGITS;
152
        int mInteractiveMinuteDigitsColor =
153
                DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_MINUTE_DIGITS;
154
        int mInteractiveSecondDigitsColor =
155
                DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_SECOND_DIGITS;
156
 
157
        /**
158
         * Whether the display supports fewer bits for each color in ambient mode. When true, we
159
         * disable anti-aliasing in ambient mode.
160
         */
161
        boolean mLowBitAmbient;
162
 
163
        @Override
164
        public void onCreate(SurfaceHolder holder) {
165
            if (Log.isLoggable(TAG, Log.DEBUG)) {
166
                Log.d(TAG, "onCreate");
167
            }
168
            super.onCreate(holder);
169
 
170
            setWatchFaceStyle(new WatchFaceStyle.Builder(DigitalWatchFaceService.this)
171
                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
172
                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
173
                    .setShowSystemUiTime(false)
174
                    .build());
175
            Resources resources = DigitalWatchFaceService.this.getResources();
176
            mYOffset = resources.getDimension(R.dimen.digital_y_offset);
177
            mAmString = resources.getString(R.string.digital_am);
178
            mPmString = resources.getString(R.string.digital_pm);
179
 
180
            mBackgroundPaint = new Paint();
181
            mBackgroundPaint.setColor(mInteractiveBackgroundColor);
182
            mHourPaint = createTextPaint(mInteractiveHourDigitsColor, BOLD_TYPEFACE);
183
            mMinutePaint = createTextPaint(mInteractiveMinuteDigitsColor);
184
            mSecondPaint = createTextPaint(mInteractiveSecondDigitsColor);
185
            mAmPmPaint = createTextPaint(resources.getColor(R.color.digital_am_pm));
186
            mColonPaint = createTextPaint(resources.getColor(R.color.digital_colons));
187
 
188
            mTime = new Time();
189
        }
190
 
191
        @Override
192
        public void onDestroy() {
193
            mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
194
            super.onDestroy();
195
        }
196
 
197
        private Paint createTextPaint(int defaultInteractiveColor) {
198
            return createTextPaint(defaultInteractiveColor, NORMAL_TYPEFACE);
199
        }
200
 
201
        private Paint createTextPaint(int defaultInteractiveColor, Typeface typeface) {
202
            Paint paint = new Paint();
203
            paint.setColor(defaultInteractiveColor);
204
            paint.setTypeface(typeface);
205
            paint.setAntiAlias(true);
206
            return paint;
207
        }
208
 
209
        @Override
210
        public void onVisibilityChanged(boolean visible) {
211
            if (Log.isLoggable(TAG, Log.DEBUG)) {
212
                Log.d(TAG, "onVisibilityChanged: " + visible);
213
            }
214
            super.onVisibilityChanged(visible);
215
 
216
            if (visible) {
217
                mGoogleApiClient.connect();
218
 
219
                registerReceiver();
220
 
221
                // Update time zone in case it changed while we weren't visible.
222
                mTime.clear(TimeZone.getDefault().getID());
223
                mTime.setToNow();
224
            } else {
225
                unregisterReceiver();
226
 
227
                if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
228
                    Wearable.DataApi.removeListener(mGoogleApiClient, this);
229
                    mGoogleApiClient.disconnect();
230
                }
231
            }
232
 
233
            // Whether the timer should be running depends on whether we're visible (as well as
234
            // whether we're in ambient mode), so we may need to start or stop the timer.
235
            updateTimer();
236
        }
237
 
238
        private void registerReceiver() {
239
            if (mRegisteredTimeZoneReceiver) {
240
                return;
241
            }
242
            mRegisteredTimeZoneReceiver = true;
243
            IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
244
            DigitalWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
245
        }
246
 
247
        private void unregisterReceiver() {
248
            if (!mRegisteredTimeZoneReceiver) {
249
                return;
250
            }
251
            mRegisteredTimeZoneReceiver = false;
252
            DigitalWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
253
        }
254
 
255
        @Override
256
        public void onApplyWindowInsets(WindowInsets insets) {
257
            if (Log.isLoggable(TAG, Log.DEBUG)) {
258
                Log.d(TAG, "onApplyWindowInsets: " + (insets.isRound() ? "round" : "square"));
259
            }
260
            super.onApplyWindowInsets(insets);
261
 
262
            // Load resources that have alternate values for round watches.
263
            Resources resources = DigitalWatchFaceService.this.getResources();
264
            boolean isRound = insets.isRound();
265
            mXOffset = resources.getDimension(isRound
266
                    ? R.dimen.digital_x_offset_round : R.dimen.digital_x_offset);
267
            float textSize = resources.getDimension(isRound
268
                    ? R.dimen.digital_text_size_round : R.dimen.digital_text_size);
269
            float amPmSize = resources.getDimension(isRound
270
                    ? R.dimen.digital_am_pm_size_round : R.dimen.digital_am_pm_size);
271
 
272
            mHourPaint.setTextSize(textSize);
273
            mMinutePaint.setTextSize(textSize);
274
            mSecondPaint.setTextSize(textSize);
275
            mAmPmPaint.setTextSize(amPmSize);
276
            mColonPaint.setTextSize(textSize);
277
 
278
            mColonWidth = mColonPaint.measureText(COLON_STRING);
279
        }
280
 
281
        @Override
282
        public void onPropertiesChanged(Bundle properties) {
283
            super.onPropertiesChanged(properties);
284
 
285
            boolean burnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false);
286
            mHourPaint.setTypeface(burnInProtection ? NORMAL_TYPEFACE : BOLD_TYPEFACE);
287
 
288
            mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
289
 
290
            if (Log.isLoggable(TAG, Log.DEBUG)) {
291
                Log.d(TAG, "onPropertiesChanged: burn-in protection = " + burnInProtection
292
                        + ", low-bit ambient = " + mLowBitAmbient);
293
            }
294
        }
295
 
296
        @Override
297
        public void onTimeTick() {
298
            super.onTimeTick();
299
            if (Log.isLoggable(TAG, Log.DEBUG)) {
300
                Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
301
            }
302
            invalidate();
303
        }
304
 
305
        @Override
306
        public void onAmbientModeChanged(boolean inAmbientMode) {
307
            super.onAmbientModeChanged(inAmbientMode);
308
            if (Log.isLoggable(TAG, Log.DEBUG)) {
309
                Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
310
            }
311
            adjustPaintColorToCurrentMode(mBackgroundPaint, mInteractiveBackgroundColor,
312
                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_BACKGROUND);
313
            adjustPaintColorToCurrentMode(mHourPaint, mInteractiveHourDigitsColor,
314
                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_HOUR_DIGITS);
315
            adjustPaintColorToCurrentMode(mMinutePaint, mInteractiveMinuteDigitsColor,
316
                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_MINUTE_DIGITS);
317
            // Actually, the seconds are not rendered in the ambient mode, so we could pass just any
318
            // value as ambientColor here.
319
            adjustPaintColorToCurrentMode(mSecondPaint, mInteractiveSecondDigitsColor,
320
                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_SECOND_DIGITS);
321
 
322
            if (mLowBitAmbient) {
323
                boolean antiAlias = !inAmbientMode;
324
                mHourPaint.setAntiAlias(antiAlias);
325
                mMinutePaint.setAntiAlias(antiAlias);
326
                mSecondPaint.setAntiAlias(antiAlias);
327
                mAmPmPaint.setAntiAlias(antiAlias);
328
                mColonPaint.setAntiAlias(antiAlias);
329
            }
330
            invalidate();
331
 
332
            // Whether the timer should be running depends on whether we're in ambient mode (as well
333
            // as whether we're visible), so we may need to start or stop the timer.
334
            updateTimer();
335
        }
336
 
337
        private void adjustPaintColorToCurrentMode(Paint paint, int interactiveColor,
338
                int ambientColor) {
339
            paint.setColor(isInAmbientMode() ? ambientColor : interactiveColor);
340
        }
341
 
342
        @Override
343
        public void onInterruptionFilterChanged(int interruptionFilter) {
344
            if (Log.isLoggable(TAG, Log.DEBUG)) {
345
                Log.d(TAG, "onInterruptionFilterChanged: " + interruptionFilter);
346
            }
347
            super.onInterruptionFilterChanged(interruptionFilter);
348
 
349
            boolean inMuteMode = interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE;
350
            // We only need to update once a minute in mute mode.
351
            setInteractiveUpdateRateMs(inMuteMode ? MUTE_UPDATE_RATE_MS : NORMAL_UPDATE_RATE_MS);
352
 
353
            if (mMute != inMuteMode) {
354
                mMute = inMuteMode;
355
                int alpha = inMuteMode ? MUTE_ALPHA : NORMAL_ALPHA;
356
                mHourPaint.setAlpha(alpha);
357
                mMinutePaint.setAlpha(alpha);
358
                mColonPaint.setAlpha(alpha);
359
                mAmPmPaint.setAlpha(alpha);
360
                invalidate();
361
            }
362
        }
363
 
364
        public void setInteractiveUpdateRateMs(long updateRateMs) {
365
            if (updateRateMs == mInteractiveUpdateRateMs) {
366
                return;
367
            }
368
            mInteractiveUpdateRateMs = updateRateMs;
369
 
370
            // Stop and restart the timer so the new update rate takes effect immediately.
371
            if (shouldTimerBeRunning()) {
372
                updateTimer();
373
            }
374
        }
375
 
376
        private void updatePaintIfInteractive(Paint paint, int interactiveColor) {
377
            if (!isInAmbientMode() && paint != null) {
378
                paint.setColor(interactiveColor);
379
            }
380
        }
381
 
382
        private void setInteractiveBackgroundColor(int color) {
383
            mInteractiveBackgroundColor = color;
384
            updatePaintIfInteractive(mBackgroundPaint, color);
385
        }
386
 
387
        private void setInteractiveHourDigitsColor(int color) {
388
            mInteractiveHourDigitsColor = color;
389
            updatePaintIfInteractive(mHourPaint, color);
390
        }
391
 
392
        private void setInteractiveMinuteDigitsColor(int color) {
393
            mInteractiveMinuteDigitsColor = color;
394
            updatePaintIfInteractive(mMinutePaint, color);
395
        }
396
 
397
        private void setInteractiveSecondDigitsColor(int color) {
398
            mInteractiveSecondDigitsColor = color;
399
            updatePaintIfInteractive(mSecondPaint, color);
400
        }
401
 
402
        private String formatTwoDigitNumber(int hour) {
403
            return String.format("%02d", hour);
404
        }
405
 
406
        private int convertTo12Hour(int hour) {
407
            int result = hour % 12;
408
            return (result == 0) ? 12 : result;
409
        }
410
 
411
        private String getAmPmString(int hour) {
412
            return (hour < 12) ? mAmString : mPmString;
413
        }
414
 
415
        @Override
416
        public void onDraw(Canvas canvas, Rect bounds) {
417
            mTime.setToNow();
418
 
419
            // Show colons for the first half of each second so the colons blink on when the time
420
            // updates.
421
            mShouldDrawColons = (System.currentTimeMillis() % 1000) < 500;
422
 
423
            // Draw the background.
424
            canvas.drawRect(0, 0, bounds.width(), bounds.height(), mBackgroundPaint);
425
 
426
            // Draw the hours.
427
            float x = mXOffset;
428
            String hourString = String.valueOf(convertTo12Hour(mTime.hour));
429
            canvas.drawText(hourString, x, mYOffset, mHourPaint);
430
            x += mHourPaint.measureText(hourString);
431
 
432
            // In ambient and mute modes, always draw the first colon. Otherwise, draw the
433
            // first colon for the first half of each second.
434
            if (isInAmbientMode() || mMute || mShouldDrawColons) {
435
                canvas.drawText(COLON_STRING, x, mYOffset, mColonPaint);
436
            }
437
            x += mColonWidth;
438
 
439
            // Draw the minutes.
440
            String minuteString = formatTwoDigitNumber(mTime.minute);
441
            canvas.drawText(minuteString, x, mYOffset, mMinutePaint);
442
            x += mMinutePaint.measureText(minuteString);
443
 
444
            // In ambient and mute modes, draw AM/PM. Otherwise, draw a second blinking
445
            // colon followed by the seconds.
446
            if (isInAmbientMode() || mMute) {
447
                x += mColonWidth;
448
                canvas.drawText(getAmPmString(mTime.hour), x, mYOffset, mAmPmPaint);
449
            } else {
450
                if (mShouldDrawColons) {
451
                    canvas.drawText(COLON_STRING, x, mYOffset, mColonPaint);
452
                }
453
                x += mColonWidth;
454
                canvas.drawText(formatTwoDigitNumber(mTime.second), x, mYOffset,
455
                        mSecondPaint);
456
            }
457
        }
458
 
459
        /**
460
         * Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
461
         * or stops it if it shouldn't be running but currently is.
462
         */
463
        private void updateTimer() {
464
            if (Log.isLoggable(TAG, Log.DEBUG)) {
465
                Log.d(TAG, "updateTimer");
466
            }
467
            mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
468
            if (shouldTimerBeRunning()) {
469
                mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
470
            }
471
        }
472
 
473
        /**
474
         * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
475
         * only run when we're visible and in interactive mode.
476
         */
477
        private boolean shouldTimerBeRunning() {
478
            return isVisible() && !isInAmbientMode();
479
        }
480
 
481
        private void updateConfigDataItemAndUiOnStartup() {
482
            DigitalWatchFaceUtil.fetchConfigDataMap(mGoogleApiClient,
483
                    new DigitalWatchFaceUtil.FetchConfigDataMapCallback() {
484
                        @Override
485
                        public void onConfigDataMapFetched(DataMap startupConfig) {
486
                            // If the DataItem hasn't been created yet or some keys are missing,
487
                            // use the default values.
488
                            setDefaultValuesForMissingConfigKeys(startupConfig);
489
                            DigitalWatchFaceUtil.putConfigDataItem(mGoogleApiClient, startupConfig);
490
 
491
                            updateUiForConfigDataMap(startupConfig);
492
                        }
493
                    }
494
            );
495
        }
496
 
497
        private void setDefaultValuesForMissingConfigKeys(DataMap config) {
498
            addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_BACKGROUND_COLOR,
499
                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_BACKGROUND);
500
            addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_HOURS_COLOR,
501
                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_HOUR_DIGITS);
502
            addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_MINUTES_COLOR,
503
                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_MINUTE_DIGITS);
504
            addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_SECONDS_COLOR,
505
                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_SECOND_DIGITS);
506
        }
507
 
508
        private void addIntKeyIfMissing(DataMap config, String key, int color) {
509
            if (!config.containsKey(key)) {
510
                config.putInt(key, color);
511
            }
512
        }
513
 
514
        @Override // DataApi.DataListener
515
        public void onDataChanged(DataEventBuffer dataEvents) {
516
            try {
517
                for (DataEvent dataEvent : dataEvents) {
518
                    if (dataEvent.getType() != DataEvent.TYPE_CHANGED) {
519
                        continue;
520
                    }
521
 
522
                    DataItem dataItem = dataEvent.getDataItem();
523
                    if (!dataItem.getUri().getPath().equals(
524
                            DigitalWatchFaceUtil.PATH_WITH_FEATURE)) {
525
                        continue;
526
                    }
527
 
528
                    DataMapItem dataMapItem = DataMapItem.fromDataItem(dataItem);
529
                    DataMap config = dataMapItem.getDataMap();
530
                    if (Log.isLoggable(TAG, Log.DEBUG)) {
531
                        Log.d(TAG, "Config DataItem updated:" + config);
532
                    }
533
                    updateUiForConfigDataMap(config);
534
                }
535
            } finally {
536
                dataEvents.close();
537
            }
538
        }
539
 
540
        private void updateUiForConfigDataMap(final DataMap config) {
541
            boolean uiUpdated = false;
542
            for (String configKey : config.keySet()) {
543
                if (!config.containsKey(configKey)) {
544
                    continue;
545
                }
546
                int color = config.getInt(configKey);
547
                if (Log.isLoggable(TAG, Log.DEBUG)) {
548
                    Log.d(TAG, "Found watch face config key: " + configKey + " -> "
549
                            + Integer.toHexString(color));
550
                }
551
                if (updateUiForKey(configKey, color)) {
552
                    uiUpdated = true;
553
                }
554
            }
555
            if (uiUpdated) {
556
                invalidate();
557
            }
558
        }
559
 
560
        /**
561
         * Updates the color of a UI item according to the given {@code configKey}. Does nothing if
562
         * {@code configKey} isn't recognized.
563
         *
564
         * @return whether UI has been updated
565
         */
566
        private boolean updateUiForKey(String configKey, int color) {
567
            if (configKey.equals(DigitalWatchFaceUtil.KEY_BACKGROUND_COLOR)) {
568
                setInteractiveBackgroundColor(color);
569
            } else if (configKey.equals(DigitalWatchFaceUtil.KEY_HOURS_COLOR)) {
570
                setInteractiveHourDigitsColor(color);
571
            } else if (configKey.equals(DigitalWatchFaceUtil.KEY_MINUTES_COLOR)) {
572
                setInteractiveMinuteDigitsColor(color);
573
            } else if (configKey.equals(DigitalWatchFaceUtil.KEY_SECONDS_COLOR)) {
574
                setInteractiveSecondDigitsColor(color);
575
            } else {
576
                Log.w(TAG, "Ignoring unknown config key: " + configKey);
577
                return false;
578
            }
579
            return true;
580
        }
581
 
582
        @Override  // GoogleApiClient.ConnectionCallbacks
583
        public void onConnected(Bundle connectionHint) {
584
            if (Log.isLoggable(TAG, Log.DEBUG)) {
585
                Log.d(TAG, "onConnected: " + connectionHint);
586
            }
587
            Wearable.DataApi.addListener(mGoogleApiClient, Engine.this);
588
            updateConfigDataItemAndUiOnStartup();
589
        }
590
 
591
        @Override  // GoogleApiClient.ConnectionCallbacks
592
        public void onConnectionSuspended(int cause) {
593
            if (Log.isLoggable(TAG, Log.DEBUG)) {
594
                Log.d(TAG, "onConnectionSuspended: " + cause);
595
            }
596
        }
597
 
598
        @Override  // GoogleApiClient.OnConnectionFailedListener
599
        public void onConnectionF