com.harlie.android.sunshine.app.WeatherWatchFace.java Source code

Java tutorial

Introduction

Here is the source code for com.harlie.android.sunshine.app.WeatherWatchFace.java

Source

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.harlie.android.sunshine.app;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.content.ContextCompat;
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.SurfaceHolder;
import android.view.WindowInsets;
import android.view.WindowManager;

import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

import me.denley.preferencebinder.PreferenceBinder;

/**
 * Analog watch face with a ticking second hand. In ambient mode, the second hand isn't shown. On
 * devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient mode.
 */
public class WeatherWatchFace extends CanvasWatchFaceService {
    private final String TAG = "LEE: <" + WeatherWatchFace.class.getSimpleName() + ">";

    private static Engine engine;

    /**
     * Update rate in milliseconds for interactive mode. We update once a second to advance the
     * second hand.
     */
    private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);

    /**
     * Handler message id for updating the time periodically in interactive mode.
     */
    private static final int MSG_UPDATE_TIME = 0;
    private static WatchFaceDesignHolder sWatchFaceDesignHolder;
    private static Context mContext;

    @Override
    public Engine onCreateEngine() {
        Log.v(TAG, "onCreateEngine");
        mContext = this.getApplicationContext();
        Log.v(TAG, "connect WearTalkService..");
        WearTalkService.connect(getContext());
        engine = new Engine();
        return engine;
    }

    public static Context getContext() {
        return mContext;
    }

    private static class EngineHandler extends Handler {
        private final WeakReference<WeatherWatchFace.Engine> mWeakReference;

        public EngineHandler(WeatherWatchFace.Engine reference) {
            mWeakReference = new WeakReference<>(reference);
        }

        @Override
        public void handleMessage(Message msg) {
            WeatherWatchFace.Engine engine = mWeakReference.get();
            if (engine != null) {
                switch (msg.what) {
                case MSG_UPDATE_TIME:
                    engine.handleUpdateTimeMessage();
                    break;
                }
            }
        }

    }

    private class Engine extends CanvasWatchFaceService.Engine {
        private final String TAG = "LEE: <" + Engine.class.getSimpleName() + ">";

        final Handler mUpdateTimeHandler = new EngineHandler(this);

        private final float CENTER_GAP_AND_CIRCLE_RADIUS = 4f;
        private boolean mRegisteredTimeZoneReceiver = false;
        private SimpleDateFormat mDateFormat;
        private Bitmap mBackgroundBitmap;
        private Bitmap mBackgroundAmbientBitmap;
        private Bitmap mBackgroundAmbientGoldBitmap;
        private Bitmap mHourHandBitmap;
        private Bitmap mHourHandGlovesBitmap;
        private Bitmap mMinuteHandBitmap;
        private Bitmap mMinuteHandGlovesBitmap;
        private Bitmap mSecondHandBitmap;
        private Bitmap mBackgroundBitmapScaled;
        private Bitmap mBackgroundAmbientBitmapScaled;
        private Bitmap mBackgroundAmbientGoldBitmapScaled;
        private Bitmap mHourHandBitmapScaled;
        private Bitmap mHourHandGlovesBitmapScaled;
        private Bitmap mMinuteHandBitmapScaled;
        private Bitmap mMinuteHandGlovesBitmapScaled;
        private Bitmap mSecondHandBitmapScaled;
        private Paint mBackgroundPaint;
        private Paint mHandPaint;
        private Paint mHandPaintShadow;
        private Paint mHandPaintAccent;
        private Paint mHandPaintBright;
        private Paint mHandPaintGold;
        private Paint mHandPaintJoint;
        private Paint mHandPaintTempHigh;
        private Paint mHandPaintTempLow;
        private Paint mHandPaintDate;
        private boolean mAmbient;
        private boolean mDaylightChanged;
        private boolean mIsRound;
        private boolean mIsJewelStudded;
        private boolean mIsDeviceMuted;
        private Calendar mCalendar;
        private int mBatteryLevel;
        private int mTapCount;
        private int mHeight;
        private int mWidth;

        /**
         * Whether the display supports fewer bits for each color in ambient mode. When true, we
         * disable anti-aliasing in ambient mode.
         */
        private boolean mLowBitAmbient;

        // receiver to update the time zone
        final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                mCalendar.setTimeZone(TimeZone.getDefault());
                invalidate();
            }
        };

        @Override
        public void onCreate(SurfaceHolder holder) {
            Log.v(TAG, "onCreate");
            super.onCreate(holder);

            setWatchFaceStyle(new WatchFaceStyle.Builder(WeatherWatchFace.this)
                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
                    .setStatusBarGravity(Gravity.TOP | Gravity.RIGHT)
                    .setHotwordIndicatorGravity(Gravity.TOP | Gravity.LEFT)
                    .setPeekOpacityMode(WatchFaceStyle.PEEK_OPACITY_MODE_TRANSLUCENT).setShowSystemUiTime(false)
                    .setAcceptsTapEvents(true).build());

            sWatchFaceDesignHolder = getWatchFaceDesignHolder();

            PreferenceBinder.bind(getContext(), sWatchFaceDesignHolder);

            // calculate current phase of the moon
            MoonCalculation moonCalculaion = new MoonCalculation();
            mCalendar = Calendar.getInstance();
            Date date = new Date();
            mCalendar.setTime(date);
            int year = mCalendar.get(Calendar.YEAR);
            int month = mCalendar.get(Calendar.MONTH) + 1;
            int day = mCalendar.get(Calendar.DAY_OF_MONTH);
            Log.v(TAG, "year=" + year + ", month=" + month + ", day=" + day);
            sWatchFaceDesignHolder.setMoonPhase(moonCalculaion.moonPhase(year, month, day));
            mDateFormat = new SimpleDateFormat("EEEE, MMM dd", Locale.getDefault());

            mHourHandBitmap = drawableToBitmap(getDrawable(R.drawable.hour_little_hand));
            mHourHandGlovesBitmap = drawableToBitmap(getDrawable(R.drawable.hour_little_hand_ambient));
            mMinuteHandBitmap = drawableToBitmap(getDrawable(R.drawable.minute_big_hand));
            mMinuteHandGlovesBitmap = drawableToBitmap(getDrawable(R.drawable.minute_big_hand_ambient));
            mSecondHandBitmap = drawableToBitmap(getDrawable(R.drawable.hypnosis));

            createPaint();

            // calculate if day or night
            int hour = mCalendar.get(Calendar.HOUR_OF_DAY);
            sWatchFaceDesignHolder.setDaytime((hour >= 6 && hour < 18));

            WindowManager wm = (WindowManager) WeatherWatchFace.getContext()
                    .getSystemService(Context.WINDOW_SERVICE);
            Display display = wm.getDefaultDisplay();
            DisplayMetrics metrics = new DisplayMetrics();
            display.getMetrics(metrics);
            mWidth = metrics.widthPixels;
            mHeight = metrics.heightPixels;

            createWatchFaceBitmaps();

            mBackgroundAmbientBitmap = drawableToBitmap(getDrawable(R.drawable.clock_face_ambient));
            mBackgroundAmbientBitmapScaled = Bitmap.createScaledBitmap(mBackgroundAmbientBitmap, mWidth, mHeight,
                    true /* filter */);
            mBackgroundAmbientGoldBitmap = drawableToBitmap(getDrawable(R.drawable.clock_face_ambient_gold));
            mBackgroundAmbientGoldBitmapScaled = Bitmap.createScaledBitmap(mBackgroundAmbientGoldBitmap, mWidth,
                    mHeight, true /* filter */);

            // sync with phone now
            WearTalkService.createSyncMessage();
        }

        public void createPaint() {
            Log.v(TAG, "createPaint");
            Resources resources = WeatherWatchFace.this.getResources();
            mBackgroundPaint = new Paint();
            mBackgroundPaint.setColor(ContextCompat.getColor(WeatherWatchFace.getContext(), R.color.background));

            mHandPaint = new Paint();
            mHandPaint.setColor(ContextCompat.getColor(WeatherWatchFace.getContext(), R.color.analog_hands));
            mHandPaint.setStrokeWidth(resources.getDimension(R.dimen.analog_hand_stroke));
            mHandPaint.setAntiAlias(true);
            mHandPaint.setStrokeCap((mIsRound) ? Paint.Cap.ROUND : Paint.Cap.SQUARE);

            mHandPaintShadow = new Paint();
            mHandPaintShadow.setColor(ContextCompat.getColor(WeatherWatchFace.getContext(), R.color.analog_hands));
            mHandPaintShadow.setStrokeWidth(resources.getDimension(R.dimen.analog_hand_stroke));
            mHandPaintShadow.setAntiAlias(true);
            mHandPaintShadow.setStrokeCap((mIsRound) ? Paint.Cap.ROUND : Paint.Cap.SQUARE);
            mHandPaintShadow.setShadowLayer(3.0f, 4.0f, 4.0f, Color.BLACK);
            mHandPaintShadow.setStyle(Paint.Style.STROKE);

            mHandPaintAccent = new Paint();
            mHandPaintAccent
                    .setColor(ContextCompat.getColor(WeatherWatchFace.getContext(), R.color.battery_warning));
            mHandPaintAccent.setStrokeWidth(resources.getDimension(R.dimen.analog_hand_stroke));
            mHandPaintAccent.setAntiAlias(true);
            mHandPaintAccent.setStrokeCap((mIsRound) ? Paint.Cap.ROUND : Paint.Cap.SQUARE);
            mHandPaintAccent.setShadowLayer(3.0f, 4.0f, 4.0f, Color.BLACK);
            mHandPaintAccent.setStyle(Paint.Style.STROKE);

            mHandPaintBright = new Paint();
            mHandPaintBright
                    .setColor(ContextCompat.getColor(WeatherWatchFace.getContext(), R.color.analog_hands_bright));
            mHandPaintBright.setStrokeWidth(resources.getDimension(R.dimen.analog_hand_stroke_bright));
            mHandPaintBright.setAntiAlias(true);
            mHandPaintBright.setStrokeCap((mIsRound) ? Paint.Cap.ROUND : Paint.Cap.SQUARE);

            mHandPaintGold = new Paint();
            mHandPaintGold
                    .setColor(ContextCompat.getColor(WeatherWatchFace.getContext(), R.color.analog_hands_gold));
            mHandPaintGold.setStrokeWidth(resources.getDimension(R.dimen.analog_hand_stroke_bright));
            mHandPaintGold.setAntiAlias(true);
            mHandPaintGold.setStrokeCap((mIsRound) ? Paint.Cap.ROUND : Paint.Cap.SQUARE);

            mHandPaintJoint = new Paint();
            mHandPaintJoint.setColor(ContextCompat.getColor(WeatherWatchFace.getContext(), R.color.analog_hands));
            mHandPaintJoint.setStrokeWidth(resources.getDimension(R.dimen.analog_hand_stroke));
            mHandPaintJoint.setAntiAlias(true);
            mHandPaintJoint.setStrokeCap((mIsRound) ? Paint.Cap.ROUND : Paint.Cap.SQUARE);

            mHandPaintTempHigh = new Paint();
            mHandPaintTempHigh
                    .setColor(ContextCompat.getColor(WeatherWatchFace.getContext(), R.color.temperature_high));
            mHandPaintTempHigh.setStrokeWidth(resources.getDimension(R.dimen.temperature_stroke));
            mHandPaintTempHigh.setAntiAlias(true);
            mHandPaintTempHigh.setTypeface(Typeface.DEFAULT);
            mHandPaintTempHigh.setTextSize(getResources().getInteger(R.integer.temperature_text_size));
            mHandPaintTempHigh.setStyle(Paint.Style.FILL);

            mHandPaintTempLow = new Paint();
            mHandPaintTempLow
                    .setColor(ContextCompat.getColor(WeatherWatchFace.getContext(), R.color.temperature_low));
            mHandPaintTempLow.setStrokeWidth(resources.getDimension(R.dimen.temperature_stroke));
            mHandPaintTempLow.setAntiAlias(true);
            mHandPaintTempLow.setTypeface(Typeface.DEFAULT);
            mHandPaintTempLow.setTextSize(getResources().getInteger(R.integer.temperature_text_size));
            mHandPaintTempLow.setStyle(Paint.Style.FILL);

            mHandPaintDate = new Paint();
            mHandPaintDate.setColor(ContextCompat.getColor(WeatherWatchFace.getContext(), R.color.current_date));
            mHandPaintDate.setStrokeWidth(resources.getDimension(R.dimen.current_date_stroke));
            mHandPaintDate.setAntiAlias(true);
            mHandPaintDate.setTypeface(Typeface.DEFAULT);
            mHandPaintDate.setTextSize(getResources().getInteger(R.integer.temperature_text_size));
            mHandPaintDate.setStyle(Paint.Style.FILL);
        }

        private void createWatchFaceBitmaps() {
            Log.v(TAG, "createWatchBitmaps");
            mBackgroundBitmap = createWatchBackgroundBitmap(getWatchFaceDesignHolder());
            scaleWatchFace(mWidth, mHeight);
        }

        private Bitmap createWatchBackgroundBitmap(WatchFaceDesignHolder watchFaceDesignHolder) {
            Log.v(TAG, "createWatchBackgroundBitmap");
            watchFaceDesignHolder.setDirty(false);
            if (watchFaceDesignHolder == null) {
                Log.v(TAG, "use default clock_face");
                mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.clock_face));
                return mBackgroundBitmap;
            }
            Bitmap overlay;
            if (watchFaceDesignHolder.useStaticBackground()) {
                if (watchFaceDesignHolder.isDaytime()) {
                    if (watchFaceDesignHolder.isOvercast()) {
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.day_overcast));
                        Log.v(TAG, "overcast - static");
                    } else {
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.day));
                        Log.v(TAG, "day - static");
                    }
                } else {
                    mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.night));
                    Log.v(TAG, "night - static");
                }
                if (watchFaceDesignHolder.useStandardFace()) {
                    overlay = drawableToBitmap(getDrawable(R.drawable.not_windy_day_standard));
                    Log.v(TAG, "not_windy_day - standard - static");
                } else {
                    overlay = drawableToBitmap(getDrawable(R.drawable.not_windy_day));
                    Log.v(TAG, "not_windy_day - alternate - static");
                }
            } else {
                if (watchFaceDesignHolder.isDaytime()) {
                    if (watchFaceDesignHolder.isSunshine()) {
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.day));
                        Log.v(TAG, "day");
                        if (!watchFaceDesignHolder.isHeavyClouds() && !watchFaceDesignHolder.isHeavyRain()
                                && !watchFaceDesignHolder.isHeavySnow() && !watchFaceDesignHolder.isHeavyStorm()) {
                            overlay = drawableToBitmap(getDrawable(R.drawable.sunny));
                            mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                            Log.v(TAG, "sunny");
                        }
                    } else if (watchFaceDesignHolder.isOvercast()) {
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.day_overcast));
                        Log.v(TAG, "overcast");
                    } else {
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.day));
                        Log.v(TAG, "day");
                    }
                } else {
                    switch (watchFaceDesignHolder.getMoonPhase()) {
                    case 1:
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.night_waxing_crescent));
                        Log.v(TAG, "night_waxing_crescent");
                        break;
                    case 2:
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.night_first_quarter));
                        Log.v(TAG, "night_first_quarter");
                        break;
                    case 3:
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.night_waxing_gibbous));
                        Log.v(TAG, "night_waxing_gibbous");
                        break;
                    case 4:
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.night_full_moon));
                        Log.v(TAG, "night_full_moon");
                        break;
                    case 5:
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.night_waning_gibbous));
                        Log.v(TAG, "night_waning_gibbous");
                        break;
                    case 6:
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.night_last_quarter));
                        Log.v(TAG, "night_last_quarter");
                        break;
                    case 7:
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.night_waning_crescent));
                        Log.v(TAG, "night_waning_crescent");
                        break;
                    case 0:
                    default:
                        mBackgroundBitmap = drawableToBitmap(getDrawable(R.drawable.night_no_moon));
                        Log.v(TAG, "night_no_moon");
                        break;
                    }
                }

                // cloudy
                if (watchFaceDesignHolder.isHeavyClouds() || watchFaceDesignHolder.isModerateClouds()
                        || watchFaceDesignHolder.isLightClouds()) {
                    if (watchFaceDesignHolder.isAreCloudsLow()) {
                        if (watchFaceDesignHolder.isAreCloudsDark()) {
                            if (watchFaceDesignHolder.isHeavyClouds() || watchFaceDesignHolder.isModerateClouds()) {
                                overlay = drawableToBitmap(getDrawable(R.drawable.moderate_dark_low_clouds));
                                Log.v(TAG, "moderate_dark_low_clouds");
                            } else {
                                overlay = drawableToBitmap(getDrawable(R.drawable.dark_low_cloud));
                                Log.v(TAG, "dark_low_cloud");
                            }
                        } else { // light clouds
                            if (watchFaceDesignHolder.isHeavyClouds() || watchFaceDesignHolder.isModerateClouds()) {
                                overlay = drawableToBitmap(getDrawable(R.drawable.moderate_light_low_clouds));
                                Log.v(TAG, "moderate_light_low_clouds");
                            } else {
                                overlay = drawableToBitmap(getDrawable(R.drawable.light_low_cloud));
                                Log.v(TAG, "light_low_cloud");
                            }
                        }
                    } else { // high clouds
                        if (watchFaceDesignHolder.isAreCloudsDark()) {
                            if (watchFaceDesignHolder.isHeavyClouds() || watchFaceDesignHolder.isModerateClouds()) {
                                overlay = drawableToBitmap(getDrawable(R.drawable.moderate_dark_high_clouds));
                                Log.v(TAG, "moderate_dark_high_clouds");
                            } else {
                                overlay = drawableToBitmap(getDrawable(R.drawable.dark_high_cloud));
                                Log.v(TAG, "dark_high_cloud");
                            }
                        } else { // light clouds
                            if (watchFaceDesignHolder.isHeavyClouds() || watchFaceDesignHolder.isModerateClouds()) {
                                overlay = drawableToBitmap(getDrawable(R.drawable.moderate_light_high_clouds));
                                Log.v(TAG, "moderate_light_high_clouds");
                            } else {
                                overlay = drawableToBitmap(getDrawable(R.drawable.light_high_cloud));
                                Log.v(TAG, "light_high_cloud");
                            }
                        }
                    }
                    if (overlay != null) {
                        mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                    }
                }

                // rainy
                if (watchFaceDesignHolder.isHeavyRain() || watchFaceDesignHolder.isModerateRain()
                        || watchFaceDesignHolder.isLightRain()) {
                    if (watchFaceDesignHolder.isHeavyRain()) {
                        overlay = drawableToBitmap(getDrawable(R.drawable.heavy_rain));
                        Log.v(TAG, "heavy_rain");
                    } else if (watchFaceDesignHolder.isModerateRain()) {
                        overlay = drawableToBitmap(getDrawable(R.drawable.moderate_rain));
                        Log.v(TAG, "moderate_rain");
                    } else {
                        overlay = drawableToBitmap(getDrawable(R.drawable.light_rain));
                        Log.v(TAG, "light_rain");
                    }
                    mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                }

                // snowy
                if (watchFaceDesignHolder.isHeavySnow() || watchFaceDesignHolder.isModerateSnow()
                        || watchFaceDesignHolder.isLightSnow()) {
                    if (watchFaceDesignHolder.isHeavySnow()) {
                        overlay = drawableToBitmap(getDrawable(R.drawable.heavy_snow));
                        Log.v(TAG, "heavy_snow");
                    } else if (watchFaceDesignHolder.isModerateSnow()) {
                        overlay = drawableToBitmap(getDrawable(R.drawable.moderate_snow));
                        Log.v(TAG, "moderate_snow");
                    } else {
                        overlay = drawableToBitmap(getDrawable(R.drawable.light_snow));
                        Log.v(TAG, "light_snow");
                    }
                    mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                }

                // stormy
                if (watchFaceDesignHolder.isHeavyStorm() || watchFaceDesignHolder.isModerateStorm()
                        || watchFaceDesignHolder.isLightStorm()) {
                    if (watchFaceDesignHolder.isHeavyStorm()) {
                        overlay = drawableToBitmap(getDrawable(R.drawable.heavy_storm));
                        Log.v(TAG, "heavy_storm");
                    } else if (watchFaceDesignHolder.isModerateStorm()) {
                        overlay = drawableToBitmap(getDrawable(R.drawable.moderate_storm));
                        Log.v(TAG, "moderate_storm");
                    } else {
                        overlay = drawableToBitmap(getDrawable(R.drawable.light_storm));
                        Log.v(TAG, "light_storm");
                    }
                    mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                }

                if (watchFaceDesignHolder.useStandardFace()) {
                    // windy
                    if (watchFaceDesignHolder.isHeavyWind() || watchFaceDesignHolder.isModerateWind()
                            || watchFaceDesignHolder.isLightWind()) {
                        overlay = drawableToBitmap(getDrawable(R.drawable.windy_day_standard));
                        Log.v(TAG, "windy_day - standard");
                    } else {
                        overlay = drawableToBitmap(getDrawable(R.drawable.not_windy_day_standard));
                        Log.v(TAG, "not_windy_day - standard");
                    }
                } else {
                    // windy
                    if (watchFaceDesignHolder.isHeavyWind() || watchFaceDesignHolder.isModerateWind()
                            || watchFaceDesignHolder.isLightWind()) {
                        overlay = drawableToBitmap(getDrawable(R.drawable.windy_day));
                        Log.v(TAG, "windy_day - alternate");
                    } else {
                        overlay = drawableToBitmap(getDrawable(R.drawable.not_windy_day));
                        Log.v(TAG, "not_windy_day - alternate");
                    }
                }
            }
            mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);

            // clock face
            mIsJewelStudded = false;
            if (watchFaceDesignHolder.useRomanNumeralsFace()) {
                if (watchFaceDesignHolder.useGoldInlay()) {
                    overlay = drawableToBitmap(getDrawable(R.drawable.clock_face_roman_gold));
                    mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                    Log.v(TAG, "clock_face_roman_gold");
                } else {
                    overlay = drawableToBitmap(getDrawable(R.drawable.clock_face_roman_plain));
                    mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                    Log.v(TAG, "clock_face_roman_plain");
                }
            } else {
                // check for special cases
                if (watchFaceDesignHolder.useGoldInlay() && watchFaceDesignHolder.usePreciousStones()) {
                    mIsJewelStudded = true;
                    overlay = drawableToBitmap(getDrawable(R.drawable.clock_face_diamond));
                    mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                    Log.v(TAG, "clock_face_diamond");
                } else if (watchFaceDesignHolder.usePreciousStones()) {
                    mIsJewelStudded = true;
                    overlay = drawableToBitmap(getDrawable(R.drawable.clock_face_ruby));
                    mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                    Log.v(TAG, "clock_face_ruby");
                } else if (watchFaceDesignHolder.useGoldInlay()) {
                    overlay = drawableToBitmap(getDrawable(R.drawable.clock_face_gold));
                    mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                    Log.v(TAG, "clock_face_gold");
                } else {
                    overlay = drawableToBitmap(getDrawable(R.drawable.clock_face));
                    mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                    Log.v(TAG, "clock_face");
                }
            }

            // face tick marks - check for special cases
            if (!watchFaceDesignHolder.useSecondHand() || mAmbient || mIsJewelStudded
                    || watchFaceDesignHolder.useHypnosis()) {
                Log.v(TAG, "tickmarks_none");
            } else if (!watchFaceDesignHolder.useStandardFace() && !watchFaceDesignHolder.useRomanNumeralsFace()
                    && watchFaceDesignHolder.useGoldInlay() && watchFaceDesignHolder.usePreciousStones()) {
                Log.v(TAG, "tickmarks_none");
            } else if (watchFaceDesignHolder.usePreciousStones() && !watchFaceDesignHolder.useGoldInlay()) {
                overlay = drawableToBitmap(getDrawable(R.drawable.tickmarks_plain));
                mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                Log.v(TAG, "tickmarks_plain");
            } else if (watchFaceDesignHolder.usePreciousStones()) {
                overlay = drawableToBitmap(getDrawable(R.drawable.tickmarks_ivory));
                mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                Log.v(TAG, "precious_stones - tickmarks_ivory");
            } else if (!watchFaceDesignHolder.useRomanNumeralsFace() && !watchFaceDesignHolder.useGoldInlay()) {
                Log.v(TAG, "tickmarks_none");
            } else if (!watchFaceDesignHolder.useRomanNumeralsFace()) {
                overlay = drawableToBitmap(getDrawable(R.drawable.tickmarks_plain));
                mBackgroundBitmap = combineImages(overlay, mBackgroundBitmap);
                Log.v(TAG, "tickmarks_plain");
            } else {
                Log.v(TAG, "tickmarks_none");
            }

            return mBackgroundBitmap;
        }

        // from: http://stackoverflow.com/questions/3674441/combining-2-images-overlayed
        public Bitmap combineImages(Bitmap topImage, Bitmap bottomImage) {
            if (topImage == null) {
                Log.w(TAG, "topImage is null!");
                return bottomImage;
            }
            if (bottomImage == null) {
                Log.w(TAG, "bottomImage is null!");
                return topImage;
            }
            Bitmap overlay = Bitmap.createBitmap(bottomImage.getWidth(), bottomImage.getHeight(),
                    bottomImage.getConfig());
            Canvas canvas = new Canvas(overlay);
            canvas.drawBitmap(bottomImage, new Matrix(), null);
            canvas.drawBitmap(topImage, 0, 0, null);
            return overlay;
        }

        // from: http://stackoverflow.com/questions/3035692/how-to-convert-a-drawable-to-a-bitmap
        public Bitmap drawableToBitmap(Drawable drawable) {
            Bitmap bitmap;

            if (drawable instanceof BitmapDrawable) {
                BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
                if (bitmapDrawable.getBitmap() != null) {
                    return bitmapDrawable.getBitmap();
                }
            }

            if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
                bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
            } else {
                bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
                        Bitmap.Config.ARGB_8888);
            }

            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            drawable.draw(canvas);
            return bitmap;
        }

        @Override
        public void onApplyWindowInsets(WindowInsets insets) {
            mIsRound = insets.isRound();
        }

        @Override
        public void onDestroy() {
            Log.v(TAG, "onDestroy");
            mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
            PreferenceBinder.unbind(getContext());
            Log.v(TAG, "disconnect WearTalkService..");
            WearTalkService.disconnect();
            super.onDestroy();
        }

        @Override
        public void onPropertiesChanged(Bundle properties) {
            Log.v(TAG, "onPropertiesChanged");
            super.onPropertiesChanged(properties);
            if (getWatchFaceDesignHolder() != null) {
                getWatchFaceDesignHolder().setDirty(true); // the watch face is out of sync now
            }
            mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
        }

        @Override
        public void onTimeTick() {
            super.onTimeTick();
            Date date = new Date();
            mCalendar.setTime(date);
            int hour = mCalendar.get(Calendar.HOUR_OF_DAY);
            boolean wasDaytime = getWatchFaceDesignHolder().isDaytime();
            getWatchFaceDesignHolder().setDaytime((hour >= 6 && hour < 18));
            if (wasDaytime != getWatchFaceDesignHolder().isDaytime()) {
                getWatchFaceDesignHolder().setDirty(true); // the watch face is out of sync now
            }
            mBatteryLevel = getBatteryLevel();
            invalidate();
        }

        @Override
        public void onAmbientModeChanged(boolean inAmbientMode) {
            Log.v(TAG, "onAmbientModeChanged");
            super.onAmbientModeChanged(inAmbientMode);
            if (mAmbient != inAmbientMode) {
                mAmbient = inAmbientMode;
                if (mLowBitAmbient) {
                    mHandPaint.setAntiAlias(!inAmbientMode);
                }
                getWatchFaceDesignHolder().setDirty(true);
            }
            invalidate();

            // Whether the timer should be running depends on whether we're visible (as well as
            // whether we're in ambient mode), so we may need to start or stop the timer.
            updateTimer();
        }

        @Override
        public void onInterruptionFilterChanged(int interruptionFilter) {
            Log.v(TAG, "onInterruptionFilterChanged");
            super.onInterruptionFilterChanged(interruptionFilter);
            mIsDeviceMuted = (interruptionFilter == android.support.wearable.watchface.WatchFaceService.INTERRUPTION_FILTER_NONE);
        }

        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            Log.v(TAG, "onSurfaceChanged");
            if (mBackgroundBitmap != null
                    && ((mBackgroundBitmap.getWidth() != width) || (mBackgroundBitmap.getHeight() != height))) {
                scaleWatchFace(width, height);
            }
            super.onSurfaceChanged(holder, format, width, height);
        }

        private void scaleWatchFace(int width, int height) {
            Log.v(TAG, "scaleWatchFace");
            mBackgroundBitmapScaled = Bitmap.createScaledBitmap(mBackgroundBitmap, width, height,
                    true /* filter */);
            float ratio = (float) width / mBackgroundBitmap.getWidth();
            mHourHandBitmapScaled = Bitmap.createScaledBitmap(mHourHandBitmap,
                    (int) (mHourHandBitmap.getWidth() * ratio), (int) (mHourHandBitmap.getHeight() * ratio), true);
            mHourHandGlovesBitmapScaled = Bitmap.createScaledBitmap(mHourHandGlovesBitmap,
                    (int) (mHourHandGlovesBitmap.getWidth() * ratio),
                    (int) (mHourHandGlovesBitmap.getHeight() * ratio), true);
            mMinuteHandBitmapScaled = Bitmap.createScaledBitmap(mMinuteHandBitmap,
                    (int) (mMinuteHandBitmap.getWidth() * ratio), (int) (mMinuteHandBitmap.getHeight() * ratio),
                    true);
            mMinuteHandGlovesBitmapScaled = Bitmap.createScaledBitmap(mMinuteHandGlovesBitmap,
                    (int) (mMinuteHandGlovesBitmap.getWidth() * ratio),
                    (int) (mMinuteHandGlovesBitmap.getHeight() * ratio), true);
            mSecondHandBitmapScaled = Bitmap.createScaledBitmap(mSecondHandBitmap,
                    (int) (mSecondHandBitmap.getWidth() * ratio), (int) (mSecondHandBitmap.getHeight() * ratio),
                    true);
        }

        /**
         * Captures tap event (and tap type) and toggles the background color if the user finishes
         * a tap.
         */
        @Override
        public void onTapCommand(int tapType, int x, int y, long eventTime) {
            Log.v(TAG, "onTapCommand");
            switch (tapType) {
            case TAP_TYPE_TOUCH:
                // The user has started touching the screen.
                break;
            case TAP_TYPE_TOUCH_CANCEL:
                // The user has started a different gesture or otherwise cancelled the tap.
                break;
            case TAP_TYPE_TAP:
                // The user has completed the tap gesture.
                mTapCount++;
                mBackgroundPaint.setColor(ContextCompat.getColor(WeatherWatchFace.getContext(),
                        mTapCount % 2 == 0 ? R.color.background : R.color.background2));
                break;
            }
            invalidate();
        }

        private String formatTemperature(int temp) {
            //char scale = (getWatchFaceDesignHolder().isMetric()) ? 'c' : 'f';
            char degree = '\u00B0';
            return String.format(Locale.getDefault(), "%02d%c", temp, degree);
        }

        @Override
        public void onDraw(Canvas canvas, Rect bounds) {
            Date date = new Date();
            mCalendar.setTime(date);

            boolean useSecondHand = getWatchFaceDesignHolder().useSecondHand();
            boolean ambientOverride = getWatchFaceDesignHolder().useContinuousOn();
            boolean realAmbientMode = (mAmbient && !ambientOverride);

            WatchFaceDesignHolder holder = getWatchFaceDesignHolder();

            if (holder.isDirty()) {
                Log.v(TAG, "*** WATCH FACE UPDATE ***");
                createWatchFaceBitmaps();
            } else {
                if (mCalendar.get(Calendar.HOUR) == 6 || mCalendar.get(Calendar.HOUR) == 18) {
                    if (!mDaylightChanged) {
                        Log.v(TAG, "*** WATCH FACE DAYLIGHT CHANGE ***");
                        mDaylightChanged = true;
                        createWatchFaceBitmaps();
                    }
                } else {
                    mDaylightChanged = false;
                }
            }

            // Draw the background.
            if (realAmbientMode) {
                canvas.drawColor(Color.BLACK);
                if (holder.useGoldInlay()) {
                    canvas.drawBitmap(mBackgroundAmbientGoldBitmapScaled, 0, 0, null);
                } else {
                    canvas.drawBitmap(mBackgroundAmbientBitmapScaled, 0, 0, null);
                }
            } else {
                if (mBackgroundBitmapScaled == null) {
                    canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mBackgroundPaint);
                } else {
                    canvas.drawBitmap(mBackgroundBitmapScaled, 0, 0, null);
                }
            }

            // Find the center. Ignore the window insets so that, on round watches with a
            // "chin", the watch face is centered on the entire screen, not just the usable portion.
            float centerX = bounds.width() / 2f;
            float centerY = bounds.height() / 2f;

            // we know there are 60 seconds in a minute and 360 degrees in a circle, so 1 second of time = 6 degrees arc

            int minutes = mCalendar.get(Calendar.MINUTE);
            float minRot = minutes / 30f * (float) Math.PI;
            float hrRot = ((mCalendar.get(Calendar.HOUR) + (minutes / 60f)) / 6f) * (float) Math.PI;

            float hrLength = centerX / 2;
            float minLength = hrLength + (hrLength / (mIsJewelStudded ? 2 : 3));

            // draw the hour and minute hands
            if (realAmbientMode) {
                if (!holder.useStandardFace() && !holder.useGoldInlay() && !holder.usePreciousStones()) {
                    // glow hour hand from Bitmap
                    Matrix matrix = new Matrix();
                    matrix.setRotate(hrRot / (float) Math.PI * 180, mHourHandGlovesBitmapScaled.getWidth() / 2,
                            mHourHandGlovesBitmapScaled.getHeight() / 2);
                    canvas.drawBitmap(mHourHandGlovesBitmapScaled, matrix, mHandPaint);

                    // glow minute hand from Bitmap
                    matrix = new Matrix();
                    matrix.setRotate(minRot / (float) Math.PI * 180, mMinuteHandGlovesBitmapScaled.getWidth() / 2,
                            mMinuteHandGlovesBitmapScaled.getHeight() / 2);
                    canvas.drawBitmap(mMinuteHandGlovesBitmapScaled, matrix, mHandPaint);
                } else {
                    // hour hand
                    float hrX = (float) Math.sin(hrRot) * hrLength;
                    float hrY = (float) -Math.cos(hrRot) * hrLength;
                    canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHandPaint);

                    // minute hand
                    float minX = (float) Math.sin(minRot) * minLength;
                    float minY = (float) -Math.cos(minRot) * minLength;
                    canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mHandPaint);
                }
            } else if (!mAmbient || (mAmbient && holder.useContinuousOn())) {
                if (holder.useStandardFace()) {
                    final float minutesRot = mCalendar.get(Calendar.MINUTE) * 6f;
                    final float hourHandOffset = mCalendar.get(Calendar.MINUTE) / 2f;
                    final float hourRot = (mCalendar.get(Calendar.HOUR) * 30) + hourHandOffset;
                    // save the canvas state before we can begin to rotate it.
                    canvas.save();
                    canvas.rotate(hourRot, centerX, centerY);
                    // draw hour hand
                    canvas.drawLine(centerX, centerY - CENTER_GAP_AND_CIRCLE_RADIUS, centerX, centerY - hrLength,
                            (holder.useGoldInlay()) ? mHandPaintGold : mHandPaintBright);
                    canvas.rotate(minutesRot - hourRot, centerX, centerY);
                    // draw minute hand
                    canvas.drawLine(centerX, centerY - CENTER_GAP_AND_CIRCLE_RADIUS, centerX, centerY - minLength,
                            (holder.useGoldInlay()) ? mHandPaintGold : mHandPaintBright);
                    // draw pivot point
                    canvas.drawCircle(centerX, centerY, CENTER_GAP_AND_CIRCLE_RADIUS, mHandPaintJoint);
                    canvas.restore();
                } else {
                    // hour hand from Bitmap
                    Matrix matrix = new Matrix();
                    matrix.setRotate(hrRot / (float) Math.PI * 180, mHourHandBitmapScaled.getWidth() / 2,
                            mHourHandBitmapScaled.getHeight() / 2);
                    canvas.drawBitmap(mHourHandBitmapScaled, matrix, mHandPaint);

                    // minute hand from Bitmap
                    matrix = new Matrix();
                    matrix.setRotate(minRot / (float) Math.PI * 180, mMinuteHandBitmapScaled.getWidth() / 2,
                            mMinuteHandBitmapScaled.getHeight() / 2);
                    canvas.drawBitmap(mMinuteHandBitmapScaled, matrix, mHandPaint);
                }
            }

            if (!mAmbient && !mIsDeviceMuted && mBatteryLevel > 0) {
                float secRot;
                float seconds;
                if ((holder.usePreciousStones() && holder.useGoldInlay()) || holder.useHypnosis()) {
                    seconds = (mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f); // calculate for sweeping second hand
                } else {
                    seconds = mCalendar.get(Calendar.SECOND); // calculate for ticking second hand
                }
                secRot = seconds / 30f * (float) Math.PI;
                if (holder.useHypnosis()) {
                    // second hand from Bitmap
                    Matrix matrix = new Matrix();
                    matrix.setRotate(secRot / (float) Math.PI * 180, mSecondHandBitmapScaled.getWidth() / 2,
                            mSecondHandBitmapScaled.getHeight() / 2);
                    canvas.drawBitmap(mSecondHandBitmapScaled, matrix, mHandPaint);
                } else if (useSecondHand) {
                    // second hand
                    float fullSecLength = minLength + (hrLength / 6);
                    // now shorten the length based on battery percentage remaining
                    float secLength = (fullSecLength * mBatteryLevel) / 100;
                    float secX = (float) Math.sin(secRot) * secLength;
                    float secY = (float) -Math.cos(secRot) * secLength;
                    float fullSecX = (float) Math.sin(secRot) * fullSecLength;
                    float fullSecY = (float) -Math.cos(secRot) * fullSecLength;
                    // we need to adjust the draw order to avoid a shadow appearing at the segment joint
                    if (mCalendar.get(Calendar.SECOND) >= 35 || mCalendar.get(Calendar.SECOND) <= 5) {
                        // draw the second hand battery segment using accent color
                        canvas.drawLine(centerX + secX, centerY + secY, centerX + fullSecX, centerY + fullSecY,
                                mHandPaintAccent);
                        // draw the second hand base segment using normal color
                        canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mHandPaintShadow);
                    } else {
                        // draw the second hand base segment using normal color
                        canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mHandPaintShadow);
                        // draw the second hand battery segment using accent color
                        canvas.drawLine(centerX + secX, centerY + secY, centerX + fullSecX, centerY + fullSecY,
                                mHandPaintAccent);
                    }
                    canvas.drawCircle(centerX, centerY, CENTER_GAP_AND_CIRCLE_RADIUS, mHandPaintJoint);
                }

                if (!holder.useStaticBackground()) {
                    float temp_Y_position = 55.0f;
                    String padding = "        ";
                    if (bounds.width() < 480) {
                        temp_Y_position = 65.0f;
                        padding = "  ";
                    }
                    String high = formatTemperature(holder.getHighTemp());
                    String low = formatTemperature(holder.getLowTemp());
                    canvas.drawText(high, centerX - mHandPaintTempHigh.measureText(high + padding), temp_Y_position,
                            mHandPaintTempHigh);
                    canvas.drawText(low, centerX + mHandPaintTempLow.measureText(padding), temp_Y_position,
                            mHandPaintTempLow);
                }

                float date_Y_position = 88.0f;
                int currentDayOfWeek = mCalendar.get(Calendar.DAY_OF_WEEK);
                String theDate = mDateFormat.format(date);
                canvas.drawText(theDate, centerX - mHandPaintDate.measureText(theDate) / 2, date_Y_position,
                        mHandPaintDate);
            }

            if ((holder.usePreciousStones() && holder.useGoldInlay()) || holder.useHypnosis()) {
                // Draw every frame as long as we're visible and in interactive mode.
                if (isVisible() && !mAmbient && !mIsDeviceMuted) {
                    invalidate(); // sweep the second hand
                }
            }
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            super.onVisibilityChanged(visible);

            if (visible) {
                registerReceiver();
                // Update time zone in case it changed while we weren't visible.
                mCalendar.setTimeZone(TimeZone.getDefault());
                //Log.v(TAG, "connecting..");
                //WearTalkService.connect(getApplicationContext());
            } else {
                unregisterReceiver();
                //Log.v(TAG, "disconnecting..");
                //WearTalkService.disconnect();
            }

            // Whether the timer should be running depends on whether we're visible (as well as
            // whether we're in ambient mode), so we may need to start or stop the timer.
            updateTimer();
        }

        private void registerReceiver() {
            if (mRegisteredTimeZoneReceiver) {
                return;
            }
            mRegisteredTimeZoneReceiver = true;
            IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
            WeatherWatchFace.this.registerReceiver(mTimeZoneReceiver, filter);
        }

        private void unregisterReceiver() {
            if (!mRegisteredTimeZoneReceiver) {
                return;
            }
            mRegisteredTimeZoneReceiver = false;
            WeatherWatchFace.this.unregisterReceiver(mTimeZoneReceiver);
        }

        /**
         * Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
         * or stops it if it shouldn't be running but currently is.
         */
        private void updateTimer() {
            mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
            if (shouldTimerBeRunning()) {
                mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
            }
        }

        /**
         * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
         * only run when we're visible and in interactive mode.
         */
        private boolean shouldTimerBeRunning() {
            return isVisible() && !isInAmbientMode();
        }

        /**
         * Handle updating the time periodically in interactive mode.
         */
        private void handleUpdateTimeMessage() {
            invalidate();
            if (shouldTimerBeRunning()) {
                long timeMs = System.currentTimeMillis();
                long delayMs = INTERACTIVE_UPDATE_RATE_MS - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
                mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
            }
        }

    }

    // from: http://stackoverflow.com/questions/28938464/android-wear-watch-face-get-battery-percentage-of-phone
    private int getBatteryLevel() {
        IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent batteryStatus = registerReceiver(null, iFilter);
        if (batteryStatus == null) {
            return 0;
        }
        return batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
    }

    // this holder controls the wearable's appearance and behavior
    synchronized public static WatchFaceDesignHolder getWatchFaceDesignHolder() {
        if (sWatchFaceDesignHolder == null) {
            sWatchFaceDesignHolder = new WatchFaceDesignHolder();
        }
        return sWatchFaceDesignHolder;
    }

}