blob: a8ab95568e201fbb2d567171ade6ec66540a35a0 [file] [log] [blame]
Griff Hazenb66d58c2014-11-20 14:20:56 -08001/*
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
17package com.example.android.wearable.watchface;
18
Kirsten Koa88b112a2014-11-25 14:24:16 -080019import android.content.BroadcastReceiver;
Griff Hazenb66d58c2014-11-20 14:20:56 -080020import android.content.ContentUris;
Kirsten Koa88b112a2014-11-25 14:24:16 -080021import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
Griff Hazenb66d58c2014-11-20 14:20:56 -080024import android.database.Cursor;
25import android.graphics.Canvas;
26import android.graphics.Color;
Filip Gruszczynskid8c38612014-12-02 10:28:01 -080027import android.graphics.Rect;
Griff Hazenb66d58c2014-11-20 14:20:56 -080028import android.net.Uri;
29import android.os.AsyncTask;
30import android.os.Handler;
31import android.os.Message;
Kirsten Koa88b112a2014-11-25 14:24:16 -080032import android.os.PowerManager;
Griff Hazenb66d58c2014-11-20 14:20:56 -080033import android.support.wearable.provider.WearableCalendarContract;
34import android.support.wearable.watchface.CanvasWatchFaceService;
35import android.support.wearable.watchface.WatchFaceStyle;
36import android.text.DynamicLayout;
37import android.text.Editable;
38import android.text.Html;
39import android.text.Layout;
40import android.text.SpannableStringBuilder;
41import android.text.TextPaint;
42import android.text.format.DateUtils;
43import android.util.Log;
44import android.view.SurfaceHolder;
45
Griff Hazenb66d58c2014-11-20 14:20:56 -080046/**
47 * Proof of concept sample watch face that demonstrates how a watch face can load calendar data.
48 */
49public class CalendarWatchFaceService extends CanvasWatchFaceService {
50 private static final String TAG = "CalendarWatchFace";
51
Griff Hazenb66d58c2014-11-20 14:20:56 -080052 @Override
53 public Engine onCreateEngine() {
54 return new Engine();
55 }
56
57 private class Engine extends CanvasWatchFaceService.Engine {
58
59 static final int BACKGROUND_COLOR = Color.BLACK;
60 static final int FOREGROUND_COLOR = Color.WHITE;
61 static final int TEXT_SIZE = 25;
62 static final int MSG_LOAD_MEETINGS = 0;
63
64 /** Editable string containing the text to draw with the number of meetings in bold. */
65 final Editable mEditable = new SpannableStringBuilder();
66
67 /** Width specified when {@link #mLayout} was created. */
68 int mLayoutWidth;
69
70 /** Layout to wrap {@link #mEditable} onto multiple lines. */
71 DynamicLayout mLayout;
72
73 /** Paint used to draw text. */
74 final TextPaint mTextPaint = new TextPaint();
75
76 int mNumMeetings;
77
78 private AsyncTask<Void, Void, Integer> mLoadMeetingsTask;
79
80 /** Handler to load the meetings once a minute in interactive mode. */
81 final Handler mLoadMeetingsHandler = new Handler() {
82 @Override
83 public void handleMessage(Message message) {
84 switch (message.what) {
85 case MSG_LOAD_MEETINGS:
86 cancelLoadMeetingTask();
87 mLoadMeetingsTask = new LoadMeetingsTask();
88 mLoadMeetingsTask.execute();
89 break;
90 }
91 }
92 };
93
Kirsten Koa88b112a2014-11-25 14:24:16 -080094 private boolean mIsReceiverRegistered;
95
96 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
97 @Override
98 public void onReceive(Context context, Intent intent) {
99 if (Intent.ACTION_PROVIDER_CHANGED.equals(intent.getAction())
100 && WearableCalendarContract.CONTENT_URI.equals(intent.getData())) {
101 cancelLoadMeetingTask();
102 mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
103 }
104 }
105 };
106
Griff Hazenb66d58c2014-11-20 14:20:56 -0800107 @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 setWatchFaceStyle(new WatchFaceStyle.Builder(CalendarWatchFaceService.this)
114 .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
115 .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
116 .setShowSystemUiTime(false)
117 .build());
118
119 mTextPaint.setColor(FOREGROUND_COLOR);
120 mTextPaint.setTextSize(TEXT_SIZE);
Kirsten Koa88b112a2014-11-25 14:24:16 -0800121
122 mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
Griff Hazenb66d58c2014-11-20 14:20:56 -0800123 }
124
125 @Override
126 public void onDestroy() {
127 mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS);
128 cancelLoadMeetingTask();
129 super.onDestroy();
130 }
131
132 @Override
Filip Gruszczynskid8c38612014-12-02 10:28:01 -0800133 public void onDraw(Canvas canvas, Rect bounds) {
Griff Hazenb66d58c2014-11-20 14:20:56 -0800134 // Create or update mLayout if necessary.
Filip Gruszczynskid8c38612014-12-02 10:28:01 -0800135 if (mLayout == null || mLayoutWidth != bounds.width()) {
136 mLayoutWidth = bounds.width();
Griff Hazenb66d58c2014-11-20 14:20:56 -0800137 mLayout = new DynamicLayout(mEditable, mTextPaint, mLayoutWidth,
138 Layout.Alignment.ALIGN_NORMAL, 1 /* spacingMult */, 0 /* spacingAdd */,
139 false /* includePad */);
140 }
141
142 // Update the contents of mEditable.
143 mEditable.clear();
144 mEditable.append(Html.fromHtml(getResources().getQuantityString(
145 R.plurals.calendar_meetings, mNumMeetings, mNumMeetings)));
146
147 // Draw the text on a solid background.
148 canvas.drawColor(BACKGROUND_COLOR);
149 mLayout.draw(canvas);
150 }
151
152 @Override
153 public void onVisibilityChanged(boolean visible) {
154 super.onVisibilityChanged(visible);
155 if (visible) {
Kirsten Koa88b112a2014-11-25 14:24:16 -0800156 IntentFilter filter = new IntentFilter(Intent.ACTION_PROVIDER_CHANGED);
157 filter.addDataScheme("content");
158 filter.addDataAuthority(WearableCalendarContract.AUTHORITY, null);
159 registerReceiver(mBroadcastReceiver, filter);
160 mIsReceiverRegistered = true;
161
Griff Hazenb66d58c2014-11-20 14:20:56 -0800162 mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
163 } else {
Kirsten Koa88b112a2014-11-25 14:24:16 -0800164 if (mIsReceiverRegistered) {
165 unregisterReceiver(mBroadcastReceiver);
166 mIsReceiverRegistered = false;
167 }
Griff Hazenb66d58c2014-11-20 14:20:56 -0800168 mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS);
Griff Hazenb66d58c2014-11-20 14:20:56 -0800169 }
170 }
171
172 private void onMeetingsLoaded(Integer result) {
173 if (result != null) {
174 mNumMeetings = result;
175 invalidate();
176 }
Griff Hazenb66d58c2014-11-20 14:20:56 -0800177 }
178
179 private void cancelLoadMeetingTask() {
180 if (mLoadMeetingsTask != null) {
181 mLoadMeetingsTask.cancel(true);
182 }
183 }
184
185 /**
186 * Asynchronous task to load the meetings from the content provider and report the number of
187 * meetings back via {@link #onMeetingsLoaded}.
188 */
189 private class LoadMeetingsTask extends AsyncTask<Void, Void, Integer> {
Kirsten Koa88b112a2014-11-25 14:24:16 -0800190 private PowerManager.WakeLock mWakeLock;
191
Griff Hazenb66d58c2014-11-20 14:20:56 -0800192 @Override
193 protected Integer doInBackground(Void... voids) {
Kirsten Koa88b112a2014-11-25 14:24:16 -0800194 PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
195 mWakeLock = powerManager.newWakeLock(
196 PowerManager.PARTIAL_WAKE_LOCK, "CalendarWatchFaceWakeLock");
197 mWakeLock.acquire();
198
Griff Hazenb66d58c2014-11-20 14:20:56 -0800199 long begin = System.currentTimeMillis();
200 Uri.Builder builder =
201 WearableCalendarContract.Instances.CONTENT_URI.buildUpon();
202 ContentUris.appendId(builder, begin);
203 ContentUris.appendId(builder, begin + DateUtils.DAY_IN_MILLIS);
Kirsten Koa88b112a2014-11-25 14:24:16 -0800204 final Cursor cursor = getContentResolver().query(builder.build(),
Griff Hazenb66d58c2014-11-20 14:20:56 -0800205 null, null, null, null);
206 int numMeetings = cursor.getCount();
207 if (Log.isLoggable(TAG, Log.VERBOSE)) {
208 Log.v(TAG, "Num meetings: " + numMeetings);
209 }
210 return numMeetings;
211 }
212
213 @Override
214 protected void onPostExecute(Integer result) {
Kirsten Koa88b112a2014-11-25 14:24:16 -0800215 releaseWakeLock();
Griff Hazenb66d58c2014-11-20 14:20:56 -0800216 onMeetingsLoaded(result);
217 }
Kirsten Koa88b112a2014-11-25 14:24:16 -0800218
219 @Override
220 protected void onCancelled() {
221 releaseWakeLock();
222 }
223
224 private void releaseWakeLock() {
225 if (mWakeLock != null) {
226 mWakeLock.release();
227 mWakeLock = null;
228 }
229 }
Griff Hazenb66d58c2014-11-20 14:20:56 -0800230 }
231 }
Kirsten Koa88b112a2014-11-25 14:24:16 -0800232}