Skip to content

Most visited

Recently visited

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

ComplicationSimpleWatchFaceService.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.app.PendingIntent;
20
import android.content.BroadcastReceiver;
21
import android.content.ComponentName;
22
import android.content.Context;
23
import android.content.Intent;
24
import android.content.IntentFilter;
25
import android.graphics.Bitmap;
26
import android.graphics.BitmapFactory;
27
import android.graphics.Canvas;
28
import android.graphics.Color;
29
import android.graphics.ColorMatrix;
30
import android.graphics.ColorMatrixColorFilter;
31
import android.graphics.Paint;
32
import android.graphics.Rect;
33
import android.graphics.Typeface;
34
import android.os.Bundle;
35
import android.os.Handler;
36
import android.os.Message;
37
import android.support.v7.graphics.Palette;
38
import android.support.wearable.complications.ComplicationData;
39
import android.support.wearable.complications.ComplicationHelperActivity;
40
import android.support.wearable.complications.ComplicationText;
41
import android.support.wearable.watchface.CanvasWatchFaceService;
42
import android.support.wearable.watchface.WatchFaceService;
43
import android.support.wearable.watchface.WatchFaceStyle;
44
import android.text.TextUtils;
45
import android.util.Log;
46
import android.util.SparseArray;
47
import android.view.SurfaceHolder;
48
 
49
import java.util.Calendar;
50
import java.util.TimeZone;
51
import java.util.concurrent.TimeUnit;
52
 
53
/**
54
 * Demonstrates two simple complications in a watch face.
55
 */
56
public class ComplicationSimpleWatchFaceService extends CanvasWatchFaceService {
57
    private static final String TAG = "SimpleComplicationWF";
58
 
59
    // Unique IDs for each complication.
60
    private static final int LEFT_DIAL_COMPLICATION = 0;
61
    private static final int RIGHT_DIAL_COMPLICATION = 1;
62
 
63
    // Left and right complication IDs as array for Complication API.
64
    public static final int[] COMPLICATION_IDS = {LEFT_DIAL_COMPLICATION, RIGHT_DIAL_COMPLICATION};
65
 
66
    // Left and right dial supported types.
67
    public static final int[][] COMPLICATION_SUPPORTED_TYPES = {
68
            {ComplicationData.TYPE_SHORT_TEXT},
69
            {ComplicationData.TYPE_SHORT_TEXT}
70
    };
71
 
72
    /*
73
     * Update rate in milliseconds for interactive mode. We update once a second to advance the
74
     * second hand.
75
     */
76
    private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
77
 
78
    @Override
79
    public Engine onCreateEngine() {
80
        return new Engine();
81
    }
82
 
83
    private class Engine extends CanvasWatchFaceService.Engine {
84
        private static final int MSG_UPDATE_TIME = 0;
85
 
86
        private static final float COMPLICATION_TEXT_SIZE = 38f;
87
        private static final int COMPLICATION_TAP_BUFFER = 40;
88
 
89
        private static final float HOUR_STROKE_WIDTH = 5f;
90
        private static final float MINUTE_STROKE_WIDTH = 3f;
91
        private static final float SECOND_TICK_STROKE_WIDTH = 2f;
92
 
93
        private static final float CENTER_GAP_AND_CIRCLE_RADIUS = 4f;
94
 
95
        private static final int SHADOW_RADIUS = 6;
96
 
97
        private Calendar mCalendar;
98
        private boolean mRegisteredTimeZoneReceiver = false;
99
        private boolean mMuteMode;
100
 
101
        private int mWidth;
102
        private int mHeight;
103
        private float mCenterX;
104
        private float mCenterY;
105
 
106
        private float mSecondHandLength;
107
        private float mMinuteHandLength;
108
        private float mHourHandLength;
109
 
110
        // Colors for all hands (hour, minute, seconds, ticks) based on photo loaded.
111
        private int mWatchHandColor;
112
        private int mWatchHandHighlightColor;
113
        private int mWatchHandShadowColor;
114
 
115
        private Paint mHourPaint;
116
        private Paint mMinutePaint;
117
        private Paint mSecondPaint;
118
        private Paint mTickAndCirclePaint;
119
 
120
        private Paint mBackgroundPaint;
121
        private Bitmap mBackgroundBitmap;
122
        private Bitmap mGrayBackgroundBitmap;
123
 
124
        // Variables for painting Complications
125
        private Paint mComplicationPaint;
126
 
127
        /* To properly place each complication, we need their x and y coordinates. While the width
128
         * may change from moment to moment based on the time, the height will not change, so we
129
         * store it as a local variable and only calculate it only when the surface changes
130
         * (onSurfaceChanged()).
131
         */
132
        private int mComplicationsY;
133
 
134
        /* Maps active complication ids to the data for that complication. Note: Data will only be
135
         * present if the user has chosen a provider via the settings activity for the watch face.
136
         */
137
        private SparseArray<ComplicationData> mActiveComplicationDataSparseArray;
138
 
139
        private boolean mAmbient;
140
        private boolean mLowBitAmbient;
141
        private boolean mBurnInProtection;
142
 
143
        private Rect mPeekCardBounds = new Rect();
144
 
145
        private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
146
            @Override
147
            public void onReceive(Context context, Intent intent) {
148
                mCalendar.setTimeZone(TimeZone.getDefault());
149
                invalidate();
150
            }
151
        };
152
 
153
        // Handler to update the time once a second in interactive mode.
154
        private final Handler mUpdateTimeHandler = new Handler() {
155
            @Override
156
            public void handleMessage(Message message) {
157
 
158
                if (Log.isLoggable(TAG, Log.DEBUG)) {
159
                    Log.d(TAG, "updating time");
160
                }
161
                invalidate();
162
                if (shouldTimerBeRunning()) {
163
                    long timeMs = System.currentTimeMillis();
164
                    long delayMs = INTERACTIVE_UPDATE_RATE_MS
165
                            - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
166
                    mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
167
                }
168
 
169
            }
170
        };
171
 
172
        @Override
173
        public void onCreate(SurfaceHolder holder) {
174
            if (Log.isLoggable(TAG, Log.DEBUG)) {
175
                Log.d(TAG, "onCreate");
176
            }
177
            super.onCreate(holder);
178
 
179
            mCalendar = Calendar.getInstance();
180
 
181
            setWatchFaceStyle(new WatchFaceStyle.Builder(ComplicationSimpleWatchFaceService.this)
182
                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
183
                    .setAcceptsTapEvents(true)
184
                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
185
                    .setShowSystemUiTime(false)
186
                    .build());
187
 
188
            initializeBackground();
189
            initializeComplication();
190
            initializeWatchFace();
191
        }
192
 
193
        private void initializeBackground() {
194
            mBackgroundPaint = new Paint();
195
            mBackgroundPaint.setColor(Color.BLACK);
196
            mBackgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg);
197
        }
198
 
199
        private void initializeComplication() {
200
            if (Log.isLoggable(TAG, Log.DEBUG)) {
201
                Log.d(TAG, "initializeComplications()");
202
            }
203
            mActiveComplicationDataSparseArray = new SparseArray<>(COMPLICATION_IDS.length);
204
 
205
            mComplicationPaint = new Paint();
206
            mComplicationPaint.setColor(Color.WHITE);
207
            mComplicationPaint.setTextSize(COMPLICATION_TEXT_SIZE);
208
            mComplicationPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
209
            mComplicationPaint.setAntiAlias(true);
210
 
211
            setActiveComplications(COMPLICATION_IDS);
212
        }
213
 
214
        private void initializeWatchFace() {
215
            /* Set defaults for colors */
216
            mWatchHandColor = Color.WHITE;
217
            mWatchHandHighlightColor = Color.RED;
218
            mWatchHandShadowColor = Color.BLACK;
219
 
220
            mHourPaint = new Paint();
221
            mHourPaint.setColor(mWatchHandColor);
222
            mHourPaint.setStrokeWidth(HOUR_STROKE_WIDTH);
223
            mHourPaint.setAntiAlias(true);
224
            mHourPaint.setStrokeCap(Paint.Cap.ROUND);
225
            mHourPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
226
 
227
            mMinutePaint = new Paint();
228
            mMinutePaint.setColor(mWatchHandColor);
229
            mMinutePaint.setStrokeWidth(MINUTE_STROKE_WIDTH);
230
            mMinutePaint.setAntiAlias(true);
231
            mMinutePaint.setStrokeCap(Paint.Cap.ROUND);
232
            mMinutePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
233
 
234
            mSecondPaint = new Paint();
235
            mSecondPaint.setColor(mWatchHandHighlightColor);
236
            mSecondPaint.setStrokeWidth(SECOND_TICK_STROKE_WIDTH);
237
            mSecondPaint.setAntiAlias(true);
238
            mSecondPaint.setStrokeCap(Paint.Cap.ROUND);
239
            mSecondPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
240
 
241
            mTickAndCirclePaint = new Paint();
242
            mTickAndCirclePaint.setColor(mWatchHandColor);
243
            mTickAndCirclePaint.setStrokeWidth(SECOND_TICK_STROKE_WIDTH);
244
            mTickAndCirclePaint.setAntiAlias(true);
245
            mTickAndCirclePaint.setStyle(Paint.Style.STROKE);
246
            mTickAndCirclePaint.setShadowLayer(SHADOW_RADIUS, 0, 0, mWatchHandShadowColor);
247
 
248
            // Asynchronous call extract colors from background image to improve watch face style.
249
            Palette.from(mBackgroundBitmap).generate(
250
                    new Palette.PaletteAsyncListener() {
251
                        public void onGenerated(Palette palette) {
252
                            /*
253
                             * Sometimes, palette is unable to generate a color palette
254
                             * so we need to check that we have one.
255
                             */
256
                            if (palette != null) {
257
                                Log.d("onGenerated", palette.toString());
258
                                mWatchHandColor = palette.getVibrantColor(Color.WHITE);
259
                                mWatchHandShadowColor = palette.getDarkMutedColor(Color.BLACK);
260
                                updateWatchHandStyle();
261
                            }
262
                        }
263
                    });
264
        }
265
 
266
        @Override
267
        public void onDestroy() {
268
            mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
269
            super.onDestroy();
270
        }
271
 
272
        @Override
273
        public void onPropertiesChanged(Bundle properties) {
274
            super.onPropertiesChanged(properties);
275
            if (Log.isLoggable(TAG, Log.DEBUG)) {
276
                Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + mLowBitAmbient);
277
            }
278
 
279
            mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
280
            mBurnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false);
281
        }
282
 
283
        /*
284
         * Called when there is updated data for a complication id.
285
         */
286
        @Override
287
        public void onComplicationDataUpdate(
288
                int complicationId, ComplicationData complicationData) {
289
            Log.d(TAG, "onComplicationDataUpdate() id: " + complicationId);
290
 
291
            // Adds/updates active complication data in the array.
292
            mActiveComplicationDataSparseArray.put(complicationId, complicationData);
293
            invalidate();
294
        }
295
 
296
        @Override
297
        public void onTapCommand(int tapType, int x, int y, long eventTime) {
298
            Log.d(TAG, "OnTapCommand()");
299
            switch (tapType) {