Home | History | Annotate | Download | only in com.example.android.wearable.watchface
      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
     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
    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
    126         public void onDestroy() {
    127             mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS);
    128             cancelLoadMeetingTask();
    129             super.onDestroy();
    130         }
    131 
    132         @Override
    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
    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 
    172         private void onMeetingsLoaded(Integer result) {
    173             if (result != null) {
    174                 mNumMeetings = result;
    175                 invalidate();
    176             }
    177         }
    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> {
    190             private PowerManager.WakeLock mWakeLock;
    191 
    192             @Override
    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
    214             protected void onPostExecute(Integer result) {
    215                 releaseWakeLock();
    216                 onMeetingsLoaded(result);
    217             }
    218 
    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             }
    230         }
    231     }
    232 }