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