Skip to content

Most visited

Recently visited

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

CalendarWatchFaceService.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.Manifest;
20
import android.content.BroadcastReceiver;
21
import android.content.ContentUris;
22
import android.content.Context;
23
import android.content.Intent;
24
import android.content.IntentFilter;
25
import android.content.pm.PackageManager;
26
import android.database.Cursor;
27
import android.graphics.Canvas;
28
import android.graphics.Color;
29
import android.graphics.Rect;
30
import android.net.Uri;
31
import android.os.AsyncTask;
32
import android.os.Handler;
33
import android.os.Message;
34
import android.os.PowerManager;
35
import android.support.v4.app.ActivityCompat;
36
import android.support.wearable.provider.WearableCalendarContract;
37
import android.support.wearable.watchface.CanvasWatchFaceService;
38
import android.support.wearable.watchface.WatchFaceService;
39
import android.support.wearable.watchface.WatchFaceStyle;
40
import android.text.DynamicLayout;
41
import android.text.Editable;
42
import android.text.Html;
43
import android.text.Layout;
44
import android.text.SpannableStringBuilder;
45
import android.text.TextPaint;
46
import android.text.format.DateUtils;
47
import android.util.Log;
48
import android.view.SurfaceHolder;
49
 
50
/**
51
 * Proof of concept sample watch face that demonstrates how a watch face can load calendar data.
52
 */
53
public class CalendarWatchFaceService extends CanvasWatchFaceService {
54
    private static final String TAG = "CalendarWatchFace";
55
 
56
    @Override
57
    public Engine onCreateEngine() {
58
        return new Engine();
59
    }
60
 
61
    private class Engine extends CanvasWatchFaceService.Engine {
62
 
63
        static final int BACKGROUND_COLOR = Color.BLACK;
64
        static final int FOREGROUND_COLOR = Color.WHITE;
65
        static final int TEXT_SIZE = 25;
66
        static final int MSG_LOAD_MEETINGS = 0;
67
 
68
        /** Editable string containing the text to draw with the number of meetings in bold. */
69
        final Editable mEditable = new SpannableStringBuilder();
70
 
71
        /** Width specified when {@link #mLayout} was created. */
72
        int mLayoutWidth;
73
 
74
        /** Layout to wrap {@link #mEditable} onto multiple lines. */
75
        DynamicLayout mLayout;
76
 
77
        /** Paint used to draw text. */
78
        final TextPaint mTextPaint = new TextPaint();
79
 
80
        int mNumMeetings;
81
        private boolean mCalendarPermissionApproved;
82
        private String mCalendarNotApprovedMessage;
83
 
84
        private AsyncTask<Void, Void, Integer> mLoadMeetingsTask;
85
 
86
        private boolean mIsReceiverRegistered;
87
 
88
        /** Handler to load the meetings once a minute in interactive mode. */
89
        final Handler mLoadMeetingsHandler = new Handler() {
90
            @Override
91
            public void handleMessage(Message message) {
92
                switch (message.what) {
93
                    case MSG_LOAD_MEETINGS:
94
 
95
                        cancelLoadMeetingTask();
96
 
97
                        // Loads meetings.
98
                        if (mCalendarPermissionApproved) {
99
                            mLoadMeetingsTask = new LoadMeetingsTask();
100
                            mLoadMeetingsTask.execute();
101
                        }
102
                        break;
103
                }
104
            }
105
        };
106
 
107
        private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
108
            @Override
109
            public void onReceive(Context context, Intent intent) {
110
                if (Intent.ACTION_PROVIDER_CHANGED.equals(intent.getAction())
111
                        && WearableCalendarContract.CONTENT_URI.equals(intent.getData())) {
112
                    mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
113
                }
114
            }
115
        };
116
 
117
        @Override
118
        public void onCreate(SurfaceHolder holder) {
119
            super.onCreate(holder);
120
            Log.d(TAG, "onCreate");
121
 
122
            mCalendarNotApprovedMessage =
123
                    getResources().getString(R.string.calendar_permission_not_approved);
124
 
125
            /* Accepts tap events to allow permission changes by user. */
126
            setWatchFaceStyle(new WatchFaceStyle.Builder(CalendarWatchFaceService.this)
127
                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
128
                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
129
                    .setShowSystemUiTime(false)
130
                    .setAcceptsTapEvents(true)
131
                    .build());
132
 
133
            mTextPaint.setColor(FOREGROUND_COLOR);
134
            mTextPaint.setTextSize(TEXT_SIZE);
135
 
136
            // Enables app to handle 23+ (M+) style permissions.
137
            mCalendarPermissionApproved =
138
                    ActivityCompat.checkSelfPermission(
139
                            getApplicationContext(),
140
                            Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED;
141
 
142
            if (mCalendarPermissionApproved) {
143
                mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
144
            }
145
        }
146
 
147
        @Override
148
        public void onDestroy() {
149
            mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS);
150
            super.onDestroy();
151
        }
152
 
153
        /*
154
         * Captures tap event (and tap type) and increments correct tap type total.
155
         */
156
        @Override
157
        public void onTapCommand(int tapType, int x, int y, long eventTime) {
158
            if (Log.isLoggable(TAG, Log.DEBUG)) {
159
                Log.d(TAG, "Tap Command: " + tapType);
160
            }
161
 
162
            // Ignore lint error (fixed in wearable support library 1.4)
163
            if (tapType == WatchFaceService.TAP_TYPE_TAP && !mCalendarPermissionApproved) {
164
                Intent permissionIntent = new Intent(
165
                        getApplicationContext(),
166
                        CalendarWatchFacePermissionActivity.class);
167
                permissionIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
168
                startActivity(permissionIntent);
169
            }
170
        }
171
 
172
        @Override
173
        public void onDraw(Canvas canvas, Rect bounds) {
174
            // Create or update mLayout if necessary.
175
            if (mLayout == null || mLayoutWidth != bounds.width()) {
176
                mLayoutWidth = bounds.width();
177
                mLayout = new DynamicLayout(mEditable, mTextPaint, mLayoutWidth,
178
                        Layout.Alignment.ALIGN_NORMAL, 1 /* spacingMult */, 0 /* spacingAdd */,
179
                        false /* includePad */);
180
            }
181
 
182
            // Update the contents of mEditable.
183
            mEditable.clear();
184
 
185
            if (mCalendarPermissionApproved) {
186
                mEditable.append(Html.fromHtml(getResources().getQuantityString(
187
                        R.plurals.calendar_meetings, mNumMeetings, mNumMeetings)));
188
            } else {
189
                mEditable.append(Html.fromHtml(mCalendarNotApprovedMessage));
190
            }
191
 
192
            // Draw the text on a solid background.
193
            canvas.drawColor(BACKGROUND_COLOR);
194
            mLayout.draw(canvas);
195
        }
196
 
197
        @Override
198
        public void onVisibilityChanged(boolean visible) {
199
            Log.d(TAG, "onVisibilityChanged()");
200
            super.onVisibilityChanged(visible);
201
            if (visible) {
202
 
203
                // Enables app to handle 23+ (M+) style permissions.
204
                mCalendarPermissionApproved = ActivityCompat.checkSelfPermission(
205
                        getApplicationContext(),
206
                        Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED;
207
 
208
                if (mCalendarPermissionApproved) {
209
                    IntentFilter filter = new IntentFilter(Intent.ACTION_PROVIDER_CHANGED);
210
                    filter.addDataScheme("content");
211
                    filter.addDataAuthority(WearableCalendarContract.AUTHORITY, null);
212
                    registerReceiver(mBroadcastReceiver, filter);
213
                    mIsReceiverRegistered = true;
214
 
215
                    mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
216
                }
217
            } else {
218
                if (mIsReceiverRegistered) {
219
                    unregisterReceiver(mBroadcastReceiver);
220
                    mIsReceiverRegistered = false;
221
                }
222
                mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS);
223
            }
224
        }
225
 
226
        private void onMeetingsLoaded(Integer result) {
227
            if (result != null) {
228
                mNumMeetings = result;
229
                invalidate();
230
            }
231
        }
232
 
233
        private void cancelLoadMeetingTask() {
234
            if (mLoadMeetingsTask != null) {
235
                mLoadMeetingsTask.cancel(true);
236
            }
237
        }
238
 
239
        /**
240
         * Asynchronous task to load the meetings from the content provider and report the number of
241
         * meetings back via {@link #onMeetingsLoaded}.
242
         */
243
        private class LoadMeetingsTask extends AsyncTask<Void, Void, Integer> {
244
            private PowerManager.WakeLock mWakeLock;
245
 
246
            @Override
247
            protected Integer doInBackground(Void... voids) {
248
                PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
249
                mWakeLock = powerManager.newWakeLock(
250
                        PowerManager.PARTIAL_WAKE_LOCK, "CalendarWatchFaceWakeLock");
251
                mWakeLock.acquire();
252
 
253
                long begin = System.currentTimeMillis();
254
                Uri.Builder builder =
255
                        WearableCalendarContract.Instances.CONTENT_URI.buildUpon();
256
                ContentUris.appendId(builder, begin);
257
                ContentUris.appendId(builder, begin + DateUtils.DAY_IN_MILLIS);
258
                final Cursor cursor = getContentResolver().query(builder.build(),
259
                        null, null, null, null);
260
                int numMeetings = cursor.getCount();
261
 
262
                Log.d(TAG, "Num meetings: " + numMeetings);
263
 
264
                return numMeetings;
265
            }
266
 
267
            @Override
268
            protected void onPostExecute(Integer result) {
269
                releaseWakeLock();
270
                onMeetingsLoaded(result);
271
            }
272
 
273
            @Override
274
            protected void onCancelled() {
275
                releaseWakeLock();
276
            }
277
 
278
            private void releaseWakeLock() {
279
                if (mWakeLock != null) {
280
                    mWakeLock.release();
281
                    mWakeLock = null;
282
                }
283
            }
284
        }
285
    }
286
}