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