Skip to content

Most visited

Recently visited

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

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