• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ContentUris;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.database.Cursor;
25 import android.graphics.Canvas;
26 import android.graphics.Color;
27 import android.graphics.Rect;
28 import android.net.Uri;
29 import android.os.AsyncTask;
30 import android.os.Handler;
31 import android.os.Message;
32 import android.os.PowerManager;
33 import android.support.wearable.provider.WearableCalendarContract;
34 import android.support.wearable.watchface.CanvasWatchFaceService;
35 import android.support.wearable.watchface.WatchFaceStyle;
36 import android.text.DynamicLayout;
37 import android.text.Editable;
38 import android.text.Html;
39 import android.text.Layout;
40 import android.text.SpannableStringBuilder;
41 import android.text.TextPaint;
42 import android.text.format.DateUtils;
43 import android.util.Log;
44 import android.view.SurfaceHolder;
45 
46 /**
47  * Proof of concept sample watch face that demonstrates how a watch face can load calendar data.
48  */
49 public class CalendarWatchFaceService extends CanvasWatchFaceService {
50     private static final String TAG = "CalendarWatchFace";
51 
52     @Override
onCreateEngine()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 
94         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 
107         @Override
onCreate(SurfaceHolder holder)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);
121 
122             mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
123         }
124 
125         @Override
onDestroy()126         public void onDestroy() {
127             mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS);
128             cancelLoadMeetingTask();
129             super.onDestroy();
130         }
131 
132         @Override
onDraw(Canvas canvas, Rect bounds)133         public void onDraw(Canvas canvas, Rect bounds) {
134             // Create or update mLayout if necessary.
135             if (mLayout == null || mLayoutWidth != bounds.width()) {
136                 mLayoutWidth = bounds.width();
137                 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
onVisibilityChanged(boolean visible)153         public void onVisibilityChanged(boolean visible) {
154             super.onVisibilityChanged(visible);
155             if (visible) {
156                 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 
162                 mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
163             } else {
164                 if (mIsReceiverRegistered) {
165                     unregisterReceiver(mBroadcastReceiver);
166                     mIsReceiverRegistered = false;
167                 }
168                 mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS);
169             }
170         }
171 
onMeetingsLoaded(Integer result)172         private void onMeetingsLoaded(Integer result) {
173             if (result != null) {
174                 mNumMeetings = result;
175                 invalidate();
176             }
177         }
178 
cancelLoadMeetingTask()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> {
190             private PowerManager.WakeLock mWakeLock;
191 
192             @Override
doInBackground(Void... voids)193             protected Integer doInBackground(Void... voids) {
194                 PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
195                 mWakeLock = powerManager.newWakeLock(
196                         PowerManager.PARTIAL_WAKE_LOCK, "CalendarWatchFaceWakeLock");
197                 mWakeLock.acquire();
198 
199                 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);
204                 final Cursor cursor = getContentResolver().query(builder.build(),
205                         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
onPostExecute(Integer result)214             protected void onPostExecute(Integer result) {
215                 releaseWakeLock();
216                 onMeetingsLoaded(result);
217             }
218 
219             @Override
onCancelled()220             protected void onCancelled() {
221                 releaseWakeLock();
222             }
223 
releaseWakeLock()224             private void releaseWakeLock() {
225                 if (mWakeLock != null) {
226                     mWakeLock.release();
227                     mWakeLock = null;
228                 }
229             }
230         }
231     }
232 }