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.Bitmap; 25 import android.graphics.Canvas; 26 import android.graphics.Paint; 27 import android.graphics.Rect; 28 import android.graphics.drawable.BitmapDrawable; 29 import android.graphics.drawable.Drawable; 30 import android.os.Bundle; 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 38 import java.util.TimeZone; 39 40 /** 41 * Sample analog watch face with a sweep second hand. In ambient mode, the second hand isn't shown. 42 * On devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient mode. 43 * The watch face is drawn with less contrast in mute mode. 44 * 45 * {@link AnalogWatchFaceService} is similar but has a ticking second hand. 46 */ 47 public class SweepWatchFaceService extends CanvasWatchFaceService { 48 private static final String TAG = "SweepWatchFaceService"; 49 50 @Override onCreateEngine()51 public Engine onCreateEngine() { 52 return new Engine(); 53 } 54 55 private class Engine extends CanvasWatchFaceService.Engine { 56 Paint mHourPaint; 57 Paint mMinutePaint; 58 Paint mSecondPaint; 59 Paint mTickPaint; 60 boolean mMute; 61 Time mTime; 62 63 final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() { 64 @Override 65 public void onReceive(Context context, Intent intent) { 66 mTime.clear(intent.getStringExtra("time-zone")); 67 mTime.setToNow(); 68 } 69 }; 70 boolean mRegisteredTimeZoneReceiver = false; 71 72 /** 73 * Whether the display supports fewer bits for each color in ambient mode. When true, we 74 * disable anti-aliasing in ambient mode. 75 */ 76 boolean mLowBitAmbient; 77 78 Bitmap mBackgroundBitmap; 79 Bitmap mBackgroundScaledBitmap; 80 81 @Override onCreate(SurfaceHolder holder)82 public void onCreate(SurfaceHolder holder) { 83 if (Log.isLoggable(TAG, Log.DEBUG)) { 84 Log.d(TAG, "onCreate"); 85 } 86 super.onCreate(holder); 87 88 setWatchFaceStyle(new WatchFaceStyle.Builder(SweepWatchFaceService.this) 89 .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT) 90 .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE) 91 .setShowSystemUiTime(false) 92 .build()); 93 94 Resources resources = SweepWatchFaceService.this.getResources(); 95 Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg); 96 mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap(); 97 98 mHourPaint = new Paint(); 99 mHourPaint.setARGB(255, 200, 200, 200); 100 mHourPaint.setStrokeWidth(5.f); 101 mHourPaint.setAntiAlias(true); 102 mHourPaint.setStrokeCap(Paint.Cap.ROUND); 103 104 mMinutePaint = new Paint(); 105 mMinutePaint.setARGB(255, 200, 200, 200); 106 mMinutePaint.setStrokeWidth(3.f); 107 mMinutePaint.setAntiAlias(true); 108 mMinutePaint.setStrokeCap(Paint.Cap.ROUND); 109 110 mSecondPaint = new Paint(); 111 mSecondPaint.setARGB(255, 255, 0, 0); 112 mSecondPaint.setStrokeWidth(2.f); 113 mSecondPaint.setAntiAlias(true); 114 mSecondPaint.setStrokeCap(Paint.Cap.ROUND); 115 116 mTickPaint = new Paint(); 117 mTickPaint.setARGB(100, 255, 255, 255); 118 mTickPaint.setStrokeWidth(2.f); 119 mTickPaint.setAntiAlias(true); 120 121 mTime = new Time(); 122 } 123 124 @Override onPropertiesChanged(Bundle properties)125 public void onPropertiesChanged(Bundle properties) { 126 super.onPropertiesChanged(properties); 127 mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false); 128 if (Log.isLoggable(TAG, Log.DEBUG)) { 129 Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + mLowBitAmbient); 130 } 131 } 132 133 @Override onTimeTick()134 public void onTimeTick() { 135 super.onTimeTick(); 136 if (Log.isLoggable(TAG, Log.DEBUG)) { 137 Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode()); 138 } 139 invalidate(); 140 } 141 142 @Override onAmbientModeChanged(boolean inAmbientMode)143 public void onAmbientModeChanged(boolean inAmbientMode) { 144 super.onAmbientModeChanged(inAmbientMode); 145 if (Log.isLoggable(TAG, Log.DEBUG)) { 146 Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode); 147 } 148 if (mLowBitAmbient) { 149 boolean antiAlias = !inAmbientMode; 150 mHourPaint.setAntiAlias(antiAlias); 151 mMinutePaint.setAntiAlias(antiAlias); 152 mSecondPaint.setAntiAlias(antiAlias); 153 mTickPaint.setAntiAlias(antiAlias); 154 } 155 invalidate(); 156 } 157 158 @Override onInterruptionFilterChanged(int interruptionFilter)159 public void onInterruptionFilterChanged(int interruptionFilter) { 160 super.onInterruptionFilterChanged(interruptionFilter); 161 boolean inMuteMode = (interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE); 162 if (mMute != inMuteMode) { 163 mMute = inMuteMode; 164 mHourPaint.setAlpha(inMuteMode ? 100 : 255); 165 mMinutePaint.setAlpha(inMuteMode ? 100 : 255); 166 mSecondPaint.setAlpha(inMuteMode ? 80 : 255); 167 invalidate(); 168 } 169 } 170 171 @Override onDraw(Canvas canvas, Rect bounds)172 public void onDraw(Canvas canvas, Rect bounds) { 173 if (Log.isLoggable(TAG, Log.VERBOSE)) { 174 Log.v(TAG, "onDraw"); 175 } 176 long now = System.currentTimeMillis(); 177 mTime.set(now); 178 int milliseconds = (int) (now % 1000); 179 180 int width = bounds.width(); 181 int height = bounds.height(); 182 183 // Draw the background, scaled to fit. 184 if (mBackgroundScaledBitmap == null 185 || mBackgroundScaledBitmap.getWidth() != width 186 || mBackgroundScaledBitmap.getHeight() != height) { 187 mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, 188 width, height, true /* filter */); 189 } 190 canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null); 191 192 // Find the center. Ignore the window insets so that, on round watches with a 193 // "chin", the watch face is centered on the entire screen, not just the usable 194 // portion. 195 float centerX = width / 2f; 196 float centerY = height / 2f; 197 198 // Draw the ticks. 199 float innerTickRadius = centerX - 10; 200 float outerTickRadius = centerX; 201 for (int tickIndex = 0; tickIndex < 12; tickIndex++) { 202 float tickRot = (float) (tickIndex * Math.PI * 2 / 12); 203 float innerX = (float) Math.sin(tickRot) * innerTickRadius; 204 float innerY = (float) -Math.cos(tickRot) * innerTickRadius; 205 float outerX = (float) Math.sin(tickRot) * outerTickRadius; 206 float outerY = (float) -Math.cos(tickRot) * outerTickRadius; 207 canvas.drawLine(centerX + innerX, centerY + innerY, 208 centerX + outerX, centerY + outerY, mTickPaint); 209 } 210 211 float seconds = mTime.second + milliseconds / 1000f; 212 float secRot = seconds / 30f * (float) Math.PI; 213 int minutes = mTime.minute; 214 float minRot = minutes / 30f * (float) Math.PI; 215 float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI; 216 217 float secLength = centerX - 20; 218 float minLength = centerX - 40; 219 float hrLength = centerX - 80; 220 221 if (!isInAmbientMode()) { 222 float secX = (float) Math.sin(secRot) * secLength; 223 float secY = (float) -Math.cos(secRot) * secLength; 224 canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint); 225 } 226 227 float minX = (float) Math.sin(minRot) * minLength; 228 float minY = (float) -Math.cos(minRot) * minLength; 229 canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mMinutePaint); 230 231 float hrX = (float) Math.sin(hrRot) * hrLength; 232 float hrY = (float) -Math.cos(hrRot) * hrLength; 233 canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHourPaint); 234 235 // Draw every frame as long as we're visible and in interactive mode. 236 if (isVisible() && !isInAmbientMode()) { 237 invalidate(); 238 } 239 } 240 241 @Override onVisibilityChanged(boolean visible)242 public void onVisibilityChanged(boolean visible) { 243 super.onVisibilityChanged(visible); 244 245 if (visible) { 246 registerReceiver(); 247 248 // Update time zone in case it changed while we weren't visible. 249 mTime.clear(TimeZone.getDefault().getID()); 250 mTime.setToNow(); 251 252 invalidate(); 253 } else { 254 unregisterReceiver(); 255 } 256 } 257 registerReceiver()258 private void registerReceiver() { 259 if (mRegisteredTimeZoneReceiver) { 260 return; 261 } 262 mRegisteredTimeZoneReceiver = true; 263 IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); 264 SweepWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter); 265 } 266 unregisterReceiver()267 private void unregisterReceiver() { 268 if (!mRegisteredTimeZoneReceiver) { 269 return; 270 } 271 mRegisteredTimeZoneReceiver = false; 272 SweepWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver); 273 } 274 } 275 } 276