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