Skip to content

Most visited

Recently visited

navigation
WatchFace / Wearable / src / com.example.android.wearable.watchface /

AnalogWatchFaceService.java

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.graphics.Bitmap;
24
import android.graphics.BitmapFactory;
25
import android.graphics.Canvas;
26
import android.graphics.Color;
27
import android.graphics.ColorMatrix;
28
import android.graphics.ColorMatrixColorFilter;
29
import android.graphics.Paint;
30
import android.graphics.Rect;
31
import android.os.Bundle;
32
import android.os.Handler;
33
import android.os.Message;
34
import android.support.v7.graphics.Palette;
35
import android.support.wearable.watchface.CanvasWatchFaceService;
36
import android.support.wearable.watchface.WatchFaceService;
37
import android.support.wearable.watchface.WatchFaceStyle;
38
import android.util.Log;
39
import android.view.SurfaceHolder;
40
 
41
import java.util.Calendar;
42
import java.util.TimeZone;
43
import java.util.concurrent.TimeUnit;
44
 
45
/**
46
 * Sample analog watch face with a ticking second hand. In ambient mode, the second hand isn't
47
 * shown. On devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient
48
 * mode. The watch face is drawn with less contrast in mute mode.
49
 *
50
 * {@link SweepWatchFaceService} is similar but has a sweep second hand.
51
 */
52
public class AnalogWatchFaceService extends CanvasWatchFaceService {
53
    private static final String TAG = "AnalogWatchFaceService";
54
 
55
    /*
56
     * Update rate in milliseconds for interactive mode. We update once a second to advance the
57
     * second hand.
58
     */
59
    private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
60
 
61
    @Override
62
    public Engine onCreateEngine() {
63
        return new Engine();
64
    }
65
 
66
    private class Engine extends CanvasWatchFaceService.Engine {
67
        private static final int MSG_UPDATE_TIME = 0;
68
 
69
        private static final float HOUR_STROKE_WIDTH = 5f;
70
        private static final float MINUTE_STROKE_WIDTH = 3f;
71
        private static final float SECOND_TICK_STROKE_WIDTH = 2f;
72
 
73
        private static final float CENTER_GAP_AND_CIRCLE_RADIUS = 4f;
74
 
75
        private static final int SHADOW_RADIUS = 6;
76
 
77
        private Calendar mCalendar;
78
        private boolean mRegisteredTimeZoneReceiver = false;
79
        private boolean mMuteMode;
80
 
81
        private float mCenterX;
82
        private float mCenterY;
83
 
84
        private float mSecondHandLength;
85
        private float sMinuteHandLength;
86
        private float sHourHandLength;
87
 
88
        /* Colors for all hands (hour, minute, seconds, ticks) based on photo loaded. */
89
        private int mWatchHandColor;
90
        private int mWatchHandHighlightColor;
91
        private int mWatchHandShadowColor;
92
 
93
        private Paint mHourPaint;
94
        private Paint mMinutePaint;
95
        private Paint mSecondPaint;
96
        private Paint mTickAndCirclePaint;
97
 
98
        private Paint mBackgroundPaint;
99
        private Bitmap mBackgroundBitmap;
100
        private Bitmap mGrayBackgroundBitmap;
101
 
102
        private boolean mAmbient;
103
        private boolean mLowBitAmbient;
104
        private boolean mBurnInProtection;
105
 
106
        private Rect mPeekCardBounds = new Rect();
107
 
108
        private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
109
            @Override
110
            public void onReceive(Context context, Intent intent) {
111
                mCalendar.setTimeZone(TimeZone.getDefault());
112
                invalidate();
113
            }
114
        };
115
 
116
        /* Handler to update the time once a second in interactive mode. */
117
        private final Handler mUpdateTimeHandler = new Handler() {
118
            @Override
119
            public void handleMessage(Message message) {
120
 
121
                if (Log.isLoggable(TAG, Log.DEBUG)) {
122
                    Log.d(TAG, "updating time");
123
                }
124
                invalidate();
125
                if (shouldTimerBeRunning()) {
126
                    long timeMs = System.currentTimeMillis();
127
                    long delayMs = INTERACTIVE_UPDATE_RATE_MS
128
                            - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
129
                    mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
130
                }
131
 
132
            }
133
        };
134
 
135
        @Override
136
        public void onCreate(SurfaceHolder holder) {
137
            if (Log.isLoggable(TAG, Log.DEBUG)) {
138
                Log.d(TAG, "onCreate");
139
            }
140
            super.onCreate(holder);
141
 
142
            setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)
143
                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
144
                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
145
                    .setShowSystemUiTime(false)
146
                    .build());
147
 
148
            mBackgroundPaint = new Paint();
149
            mBackgroundPaint.setColor(Color.BLACK);
150
            mBackgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg);
151
 
152
            /* Set defaults for colors */
153
            mWatchHandColor = Color.WHITE;
154
            mWatchHandHighlightColor = Color.RED;
155
            mWatchHandShadowColor = Color.BLACK;
156
 
157
            mHourPaint = new Paint();
158
            mHourPaint.setColor(mWatchHandColor);
159
            mHourPaint.setStrokeWidth(HOUR_STROKE_WIDTH);
160
            mHourPaint.setAntiAlias(true);
161
            mHourPaint.setStrokeCap(Paint.Cap.ROUND);
162
            mHourPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
163
 
164
            mMinutePaint = new Paint();
165
            mMinutePaint.setColor(mWatchHandColor);
166
            mMinutePaint.setStrokeWidth(MINUTE_STROKE_WIDTH);
167
            mMinutePaint.setAntiAlias(true);
168
            mMinutePaint.setStrokeCap(Paint.Cap.ROUND);
169
            mMinutePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
170
 
171
            mSecondPaint = new Paint();
172
            mSecondPaint.setColor(mWatchHandHighlightColor);
173
            mSecondPaint.setStrokeWidth(SECOND_TICK_STROKE_WIDTH);
174
            mSecondPaint.setAntiAlias(true);
175
            mSecondPaint.setStrokeCap(Paint.Cap.ROUND);
176
            mSecondPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
177
 
178
            mTickAndCirclePaint = new Paint();
179
            mTickAndCirclePaint.setColor(mWatchHandColor);
180
            mTickAndCirclePaint.setStrokeWidth(SECOND_TICK_STROKE_WIDTH);
181
            mTickAndCirclePaint.setAntiAlias(true);
182
            mTickAndCirclePaint.setStyle(Paint.Style.STROKE);
183
            mTickAndCirclePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
184
 
185
            /* Extract colors from background image to improve watchface style. */
186
            Palette.generateAsync(
187
                    mBackgroundBitmap,
188
                    new Palette.PaletteAsyncListener() {
189
                        @Override
190
                        public void onGenerated(Palette palette) {
191
                            if (palette != null) {
192
                                if (Log.isLoggable(TAG, Log.DEBUG)) {
193
                                    Log.d(TAG, "Palette: " + palette);
194
                                }
195
 
196
                                mWatchHandHighlightColor = palette.getVibrantColor(Color.RED);
197
                                mWatchHandColor = palette.getLightVibrantColor(Color.WHITE);
198
                                mWatchHandShadowColor = palette.getDarkMutedColor(Color.BLACK);
199
                                updateWatchHandStyle();
200
                            }
201
                        }
202
                    });
203
 
204
            mCalendar = Calendar.getInstance();
205
        }
206
 
207
        @Override
208
        public void onDestroy() {
209
            mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
210
            super.onDestroy();
211
        }
212
 
213
        @Override
214
        public void onPropertiesChanged(Bundle properties) {
215
            super.onPropertiesChanged(properties);
216
            if (Log.isLoggable(TAG, Log.DEBUG)) {
217
                Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + mLowBitAmbient);
218
            }
219
 
220
            mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
221
            mBurnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false);
222
        }
223
 
224
        @Override
225
        public void onTimeTick() {
226
            super.onTimeTick();
227
            invalidate();
228
        }
229
 
230
        @Override
231
        public void onAmbientModeChanged(boolean inAmbientMode) {
232
            super.onAmbientModeChanged(inAmbientMode);
233
            if (Log.isLoggable(TAG, Log.DEBUG)) {
234
                Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
235
            }
236
            mAmbient = inAmbientMode;
237
 
238
            updateWatchHandStyle();
239
 
240
            /* Check and trigger whether or not timer should be running (only in active mode). */
241
            updateTimer();
242
        }
243
 
244
        private void updateWatchHandStyle(){
245
            if (mAmbient){
246
                mHourPaint.setColor(Color.WHITE);
247
                mMinutePaint.setColor(Color.WHITE);
248
                mSecondPaint.setColor(Color.WHITE);
249
                mTickAndCirclePaint.setColor(Color.WHITE);
250
 
251
                mHourPaint.setAntiAlias(false);
252
                mMinutePaint.setAntiAlias(false);
253
                mSecondPaint.setAntiAlias(false);
254
                mTickAndCirclePaint.setAntiAlias(false);
255
 
256
                mHourPaint.clearShadowLayer();
257
                mMinutePaint.clearShadowLayer();
258
                mSecondPaint.clearShadowLayer();
259
                mTickAndCirclePaint.clearShadowLayer();
260
 
261
            } else {
262
                mHourPaint.setColor(mWatchHandColor);
263
                mMinutePaint.setColor(mWatchHandColor);
264
                mSecondPaint.setColor(mWatchHandHighlightColor);
265
                mTickAndCirclePaint.setColor(mWatchHandColor);
266
 
267
                mHourPaint.setAntiAlias(true);
268
                mMinutePaint.setAntiAlias(true);
269
                mSecondPaint.setAntiAlias(true);
270
                mTickAndCirclePaint.setAntiAlias(true);
271
 
272
                mHourPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
273
                mMinutePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
274
                mSecondPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
275
                mTickAndCirclePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
276
            }
277
        }
278
 
279
        @Override
280
        public void onInterruptionFilterChanged(int interruptionFilter) {
281
            super.onInterruptionFilterChanged(interruptionFilter);
282
            boolean inMuteMode = (interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE);
283
 
284
            /* Dim display in mute mode. */
285
            if (mMuteMode != inMuteMode) {
286
                mMuteMode = inMuteMode;
287
                mHourPaint.setAlpha(inMuteMode ? 100 : 255);
288
                mMinutePaint.setAlpha(inMuteMode ? 100 : 255);
289
                mSecondPaint.setAlpha(inMuteMode ? 80 : 255);
290
                invalidate();
291
            }
292
        }
293
 
294
        @Override
295
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
296
            super.onSurfaceChanged(holder, format, width, height);
297
 
298
            /*
299
             * Find the coordinates of the center point on the screen, and ignore the window
300
             * insets, so that, on round watches with a "chin", the watch face is centered on the
301
             * entire screen, not just the usable portion.
302
             */
303
            mCenterX = width / 2f;
304
            mCenterY = height / 2f;
305
 
306
            /*
307
             * Calculate lengths of different hands based on watch screen size.
308
             */
309
            mSecondHandLength = (float) (mCenterX * 0.875);
310
            sMinuteHandLength = (float) (mCenterX * 0.75);
311
            sHourHandLength = (float) (mCenterX * 0.5);
312
 
313
 
314
            /* Scale loaded background image (more efficient) if surface dimensions change. */
315
            float scale = ((float) width) / (float) mBackgroundBitmap.getWidth();
316
 
317
            mBackgroundBitmap = Bitmap.cr