Skip to content

Most visited

Recently visited

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

DigitalWatchFaceService.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.content.res.Resources;
24
import android.graphics.Canvas;
25
import android.graphics.Paint;
26
import android.graphics.Rect;
27
import android.graphics.Typeface;
28
import android.os.Bundle;
29
import android.os.Handler;
30
import android.os.Message;
31
import android.support.v4.content.ContextCompat;
32
import android.support.wearable.watchface.CanvasWatchFaceService;
33
import android.support.wearable.watchface.WatchFaceService;
34
import android.support.wearable.watchface.WatchFaceStyle;
35
import android.text.format.DateFormat;
36
import android.util.Log;
37
import android.view.SurfaceHolder;
38
import android.view.WindowInsets;
39
 
40
import com.google.android.gms.common.ConnectionResult;
41
import com.google.android.gms.common.api.GoogleApiClient;
42
import com.google.android.gms.wearable.DataApi;
43
import com.google.android.gms.wearable.DataEvent;
44
import com.google.android.gms.wearable.DataEventBuffer;
45
import com.google.android.gms.wearable.DataItem;
46
import com.google.android.gms.wearable.DataMap;
47
import com.google.android.gms.wearable.DataMapItem;
48
import com.google.android.gms.wearable.Wearable;
49
 
50
import java.text.SimpleDateFormat;
51
import java.util.Calendar;
52
import java.util.Date;
53
import java.util.Locale;
54
import java.util.TimeZone;
55
import java.util.concurrent.TimeUnit;
56
 
57
/**
58
 * Sample digital watch face with blinking colons and seconds. In ambient mode, the seconds are
59
 * replaced with an AM/PM indicator and the colons don't blink. On devices with low-bit ambient
60
 * mode, the text is drawn without anti-aliasing in ambient mode. On devices which require burn-in
61
 * protection, the hours are drawn in normal rather than bold. The time is drawn with less contrast
62
 * and without seconds in mute mode.
63
 */
64
public class DigitalWatchFaceService extends CanvasWatchFaceService {
65
    private static final String TAG = "DigitalWatchFaceService";
66
 
67
    private static final Typeface BOLD_TYPEFACE =
68
            Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
69
    private static final Typeface NORMAL_TYPEFACE =
70
            Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
71
 
72
    /**
73
     * Update rate in milliseconds for normal (not ambient and not mute) mode. We update twice
74
     * a second to blink the colons.
75
     */
76
    private static final long NORMAL_UPDATE_RATE_MS = 500;
77
 
78
    /**
79
     * Update rate in milliseconds for mute mode. We update every minute, like in ambient mode.
80
     */
81
    private static final long MUTE_UPDATE_RATE_MS = TimeUnit.MINUTES.toMillis(1);
82
 
83
    @Override
84
    public Engine onCreateEngine() {
85
        return new Engine();
86
    }
87
 
88
    private class Engine extends CanvasWatchFaceService.Engine implements DataApi.DataListener,
89
            GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
90
        static final String COLON_STRING = ":";
91
 
92
        /** Alpha value for drawing time when in mute mode. */
93
        static final int MUTE_ALPHA = 100;
94
 
95
        /** Alpha value for drawing time when not in mute mode. */
96
        static final int NORMAL_ALPHA = 255;
97
 
98
        static final int MSG_UPDATE_TIME = 0;
99
 
100
        /** How often {@link #mUpdateTimeHandler} ticks in milliseconds. */
101
        long mInteractiveUpdateRateMs = NORMAL_UPDATE_RATE_MS;
102
 
103
        /** Handler to update the time periodically in interactive mode. */
104
        final Handler mUpdateTimeHandler = new Handler() {
105
            @Override
106
            public void handleMessage(Message message) {
107
                switch (message.what) {
108
                    case MSG_UPDATE_TIME:
109
                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
110
                            Log.v(TAG, "updating time");
111
                        }
112
                        invalidate();
113
                        if (shouldTimerBeRunning()) {
114
                            long timeMs = System.currentTimeMillis();
115
                            long delayMs =
116
                                    mInteractiveUpdateRateMs - (timeMs % mInteractiveUpdateRateMs);
117
                            mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
118
                        }
119
                        break;
120
                }
121
            }
122
        };
123
 
124
        GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(DigitalWatchFaceService.this)
125
                .addConnectionCallbacks(this)
126
                .addOnConnectionFailedListener(this)
127
                .addApi(Wearable.API)
128
                .build();
129
 
130
        /**
131
         * Handles time zone and locale changes.
132
         */
133
        final BroadcastReceiver mReceiver = new BroadcastReceiver() {
134
            @Override
135
            public void onReceive(Context context, Intent intent) {
136
                mCalendar.setTimeZone(TimeZone.getDefault());
137
                initFormats();
138
                invalidate();
139
            }
140
        };
141
 
142
        /**
143
         * Unregistering an unregistered receiver throws an exception. Keep track of the
144
         * registration state to prevent that.
145
         */
146
        boolean mRegisteredReceiver = false;
147
 
148
        Paint mBackgroundPaint;
149
        Paint mDatePaint;
150
        Paint mHourPaint;
151
        Paint mMinutePaint;
152
        Paint mSecondPaint;
153
        Paint mAmPmPaint;
154
        Paint mColonPaint;
155
        float mColonWidth;
156
        boolean mMute;
157
 
158
        Calendar mCalendar;
159
        Date mDate;
160
        SimpleDateFormat mDayOfWeekFormat;
161
        java.text.DateFormat mDateFormat;
162
 
163
        boolean mShouldDrawColons;
164
        float mXOffset;
165
        float mYOffset;
166
        float mLineHeight;
167
        String mAmString;
168
        String mPmString;
169
        int mInteractiveBackgroundColor =
170
                DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_BACKGROUND;
171
        int mInteractiveHourDigitsColor =
172
                DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_HOUR_DIGITS;
173
        int mInteractiveMinuteDigitsColor =
174
                DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_MINUTE_DIGITS;
175
        int mInteractiveSecondDigitsColor =
176
                DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_SECOND_DIGITS;
177
 
178
        /**
179
         * Whether the display supports fewer bits for each color in ambient mode. When true, we
180
         * disable anti-aliasing in ambient mode.
181
         */
182
        boolean mLowBitAmbient;
183
 
184
        @Override
185
        public void onCreate(SurfaceHolder holder) {
186
            if (Log.isLoggable(TAG, Log.DEBUG)) {
187
                Log.d(TAG, "onCreate");
188
            }
189
            super.onCreate(holder);
190
 
191
            setWatchFaceStyle(new WatchFaceStyle.Builder(DigitalWatchFaceService.this)
192
                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
193
                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
194
                    .setShowSystemUiTime(false)
195
                    .build());
196
            Resources resources = DigitalWatchFaceService.this.getResources();
197
            mYOffset = resources.getDimension(R.dimen.digital_y_offset);
198
            mLineHeight = resources.getDimension(R.dimen.digital_line_height);
199
            mAmString = resources.getString(R.string.digital_am);
200
            mPmString = resources.getString(R.string.digital_pm);
201
 
202
            mBackgroundPaint = new Paint();
203
            mBackgroundPaint.setColor(mInteractiveBackgroundColor);
204
            mDatePaint = createTextPaint(
205
                    ContextCompat.getColor(getApplicationContext(), R.color.digital_date));
206
            mHourPaint = createTextPaint(mInteractiveHourDigitsColor, BOLD_TYPEFACE);
207
            mMinutePaint = createTextPaint(mInteractiveMinuteDigitsColor);
208
            mSecondPaint = createTextPaint(mInteractiveSecondDigitsColor);
209
            mAmPmPaint = createTextPaint(
210
                    ContextCompat.getColor(getApplicationContext(), R.color.digital_am_pm));
211
            mColonPaint = createTextPaint(
212
                    ContextCompat.getColor(getApplicationContext(), R.color.digital_colons));
213
 
214
            mCalendar = Calendar.getInstance();
215
            mDate = new Date();
216
            initFormats();
217
        }
218
 
219
        @Override
220
        public void onDestroy() {
221
            mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
222
            super.onDestroy();
223
        }
224
 
225
        private Paint createTextPaint(int defaultInteractiveColor) {
226
            return createTextPaint(defaultInteractiveColor, NORMAL_TYPEFACE);
227
        }
228
 
229
        private Paint createTextPaint(int defaultInteractiveColor, Typeface typeface) {
230
            Paint paint = new Paint();
231
            paint.setColor(defaultInteractiveColor);
232
            paint.setTypeface(typeface);
233
            paint.setAntiAlias(true);
234
            return paint;
235
        }
236
 
237
        @Override
238
        public void onVisibilityChanged(boolean visible) {
239
            if (Log.isLoggable(TAG, Log.DEBUG)) {
240
                Log.d(TAG, "onVisibilityChanged: " + visible);
241
            }
242
            super.onVisibilityChanged(visible);
243
 
244
            if (visible) {
245
                mGoogleApiClient.connect();
246
 
247
                registerReceiver();
248
 
249
                // Update time zone and date formats, in case they changed while we weren't visible.
250
                mCalendar.setTimeZone(TimeZone.getDefault());
251
                initFormats();
252
            } else {
253
                unregisterReceiver();
254
 
255
                if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
256
                    Wearable.DataApi.removeListener(mGoogleApiClient, this);
257
                    mGoogleApiClient.disconnect();
258
                }
259
            }
260
 
261
            // Whether the timer should be running depends on whether we're visible (as well as
262
            // whether we're in ambient mode), so we may need to start or stop the timer.
263
            updateTimer();
264
        }
265
 
266
        private void initFormats() {
267
            mDayOfWeekFormat = new SimpleDateFormat("EEEE", Locale.getDefault());
268
            mDayOfWeekFormat.setCalendar(mCalendar);
269
            mDateFormat = DateFormat.getDateFormat(DigitalWatchFaceService.this);
270
            mDateFormat.setCalendar(mCalendar);
271
        }
272
 
273
        private void registerReceiver() {
274
            if (mRegisteredReceiver) {
275
                return;
276
            }
277
            mRegisteredReceiver = true;
278
            IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
279
            filter.addAction(Intent.ACTION_LOCALE_CHANGED);
280
            DigitalWatchFaceService.this.registerReceiver(mReceiver, filter);
281
        }
282
 
283
        private void unregisterReceiver() {
284
            if (!mRegisteredReceiver) {
285
                return;
286
            }
287
            mRegisteredReceiver = false;
288
            DigitalWatchFaceService.this.unregisterReceiver(mReceiver);
289
        }
290
 
291
        @Override
292
        public void onApplyWindowInsets(WindowInsets insets) {
293
            if (Log.isLoggable(TAG, Log.DEBUG)) {
294
                Log.d(TAG, "onApplyWindowInsets: " + (insets.isRound() ? "round" : "square"));
295
            }
296
            super.onApplyWindowInsets(insets);
297
 
298
            // Load resources that have alternate values for round watches.
299