• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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