| /* |
| * 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.example.android.wearable.watchface; |
| |
| 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.Paint; |
| import android.graphics.Rect; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.os.Bundle; |
| import android.support.wearable.watchface.CanvasWatchFaceService; |
| import android.support.wearable.watchface.WatchFaceService; |
| import android.support.wearable.watchface.WatchFaceStyle; |
| import android.util.Log; |
| import android.view.SurfaceHolder; |
| |
| import java.util.Calendar; |
| import java.util.TimeZone; |
| |
| /** |
| * Sample analog watch face with a sweep 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. |
| * The watch face is drawn with less contrast in mute mode. |
| * |
| * {@link AnalogWatchFaceService} is similar but has a ticking second hand. |
| */ |
| public class SweepWatchFaceService extends CanvasWatchFaceService { |
| private static final String TAG = "SweepWatchFaceService"; |
| |
| @Override |
| public Engine onCreateEngine() { |
| return new Engine(); |
| } |
| |
| private class Engine extends CanvasWatchFaceService.Engine { |
| static final float TWO_PI = (float) Math.PI * 2f; |
| |
| Paint mHourPaint; |
| Paint mMinutePaint; |
| Paint mSecondPaint; |
| Paint mTickPaint; |
| boolean mMute; |
| Calendar mCalendar; |
| |
| final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| mCalendar.setTimeZone(TimeZone.getDefault()); |
| invalidate(); |
| } |
| }; |
| boolean mRegisteredTimeZoneReceiver = false; |
| |
| /** |
| * Whether the display supports fewer bits for each color in ambient mode. When true, we |
| * disable anti-aliasing in ambient mode. |
| */ |
| boolean mLowBitAmbient; |
| |
| Bitmap mBackgroundBitmap; |
| Bitmap mBackgroundScaledBitmap; |
| |
| @Override |
| public void onCreate(SurfaceHolder holder) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onCreate"); |
| } |
| super.onCreate(holder); |
| |
| setWatchFaceStyle(new WatchFaceStyle.Builder(SweepWatchFaceService.this) |
| .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT) |
| .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE) |
| .setShowSystemUiTime(false) |
| .build()); |
| |
| Resources resources = SweepWatchFaceService.this.getResources(); |
| Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg, null /* theme */); |
| mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap(); |
| |
| mHourPaint = new Paint(); |
| mHourPaint.setARGB(255, 200, 200, 200); |
| mHourPaint.setStrokeWidth(5.f); |
| mHourPaint.setAntiAlias(true); |
| mHourPaint.setStrokeCap(Paint.Cap.ROUND); |
| |
| mMinutePaint = new Paint(); |
| mMinutePaint.setARGB(255, 200, 200, 200); |
| mMinutePaint.setStrokeWidth(3.f); |
| mMinutePaint.setAntiAlias(true); |
| mMinutePaint.setStrokeCap(Paint.Cap.ROUND); |
| |
| mSecondPaint = new Paint(); |
| mSecondPaint.setARGB(255, 255, 0, 0); |
| mSecondPaint.setStrokeWidth(2.f); |
| mSecondPaint.setAntiAlias(true); |
| mSecondPaint.setStrokeCap(Paint.Cap.ROUND); |
| |
| mTickPaint = new Paint(); |
| mTickPaint.setARGB(100, 255, 255, 255); |
| mTickPaint.setStrokeWidth(2.f); |
| mTickPaint.setAntiAlias(true); |
| |
| mCalendar = Calendar.getInstance(); |
| } |
| |
| @Override |
| public void onPropertiesChanged(Bundle properties) { |
| super.onPropertiesChanged(properties); |
| mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + mLowBitAmbient); |
| } |
| } |
| |
| @Override |
| public void onTimeTick() { |
| super.onTimeTick(); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode()); |
| } |
| invalidate(); |
| } |
| |
| @Override |
| public void onAmbientModeChanged(boolean inAmbientMode) { |
| super.onAmbientModeChanged(inAmbientMode); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode); |
| } |
| if (mLowBitAmbient) { |
| boolean antiAlias = !inAmbientMode; |
| mHourPaint.setAntiAlias(antiAlias); |
| mMinutePaint.setAntiAlias(antiAlias); |
| mSecondPaint.setAntiAlias(antiAlias); |
| mTickPaint.setAntiAlias(antiAlias); |
| } |
| invalidate(); |
| } |
| |
| @Override |
| public void onInterruptionFilterChanged(int interruptionFilter) { |
| super.onInterruptionFilterChanged(interruptionFilter); |
| boolean inMuteMode = (interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE); |
| if (mMute != inMuteMode) { |
| mMute = inMuteMode; |
| mHourPaint.setAlpha(inMuteMode ? 100 : 255); |
| mMinutePaint.setAlpha(inMuteMode ? 100 : 255); |
| mSecondPaint.setAlpha(inMuteMode ? 80 : 255); |
| invalidate(); |
| } |
| } |
| |
| @Override |
| public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { |
| if (mBackgroundScaledBitmap == null |
| || mBackgroundScaledBitmap.getWidth() != width |
| || mBackgroundScaledBitmap.getHeight() != height) { |
| mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, |
| width, height, true /* filter */); |
| } |
| super.onSurfaceChanged(holder, format, width, height); |
| } |
| |
| @Override |
| public void onDraw(Canvas canvas, Rect bounds) { |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "onDraw"); |
| } |
| long now = System.currentTimeMillis(); |
| mCalendar.setTimeInMillis(now); |
| |
| int width = bounds.width(); |
| int height = bounds.height(); |
| |
| // Draw the background, scaled to fit. |
| canvas.drawBitmap(mBackgroundScaledBitmap, 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 = width / 2f; |
| float centerY = height / 2f; |
| |
| // Draw the ticks. |
| float innerTickRadius = centerX - 10; |
| float outerTickRadius = centerX; |
| for (int tickIndex = 0; tickIndex < 12; tickIndex++) { |
| float tickRot = (float) (tickIndex * Math.PI * 2 / 12); |
| float innerX = (float) Math.sin(tickRot) * innerTickRadius; |
| float innerY = (float) -Math.cos(tickRot) * innerTickRadius; |
| float outerX = (float) Math.sin(tickRot) * outerTickRadius; |
| float outerY = (float) -Math.cos(tickRot) * outerTickRadius; |
| canvas.drawLine(centerX + innerX, centerY + innerY, |
| centerX + outerX, centerY + outerY, mTickPaint); |
| } |
| |
| float seconds = |
| mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f; |
| float secRot = seconds / 60f * TWO_PI; |
| float minutes = mCalendar.get(Calendar.MINUTE) + seconds / 60f; |
| float minRot = minutes / 60f * TWO_PI; |
| float hours = mCalendar.get(Calendar.HOUR) + minutes / 60f; |
| float hrRot = hours / 12f * TWO_PI; |
| |
| float secLength = centerX - 20; |
| float minLength = centerX - 40; |
| float hrLength = centerX - 80; |
| |
| if (!isInAmbientMode()) { |
| float secX = (float) Math.sin(secRot) * secLength; |
| float secY = (float) -Math.cos(secRot) * secLength; |
| canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint); |
| } |
| |
| float minX = (float) Math.sin(minRot) * minLength; |
| float minY = (float) -Math.cos(minRot) * minLength; |
| canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mMinutePaint); |
| |
| float hrX = (float) Math.sin(hrRot) * hrLength; |
| float hrY = (float) -Math.cos(hrRot) * hrLength; |
| canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHourPaint); |
| |
| // Draw every frame as long as we're visible and in interactive mode. |
| if (isVisible() && !isInAmbientMode()) { |
| invalidate(); |
| } |
| } |
| |
| @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()); |
| |
| invalidate(); |
| } else { |
| unregisterReceiver(); |
| } |
| } |
| |
| private void registerReceiver() { |
| if (mRegisteredTimeZoneReceiver) { |
| return; |
| } |
| mRegisteredTimeZoneReceiver = true; |
| IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); |
| SweepWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter); |
| } |
| |
| private void unregisterReceiver() { |
| if (!mRegisteredTimeZoneReceiver) { |
| return; |
| } |
| mRegisteredTimeZoneReceiver = false; |
| SweepWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver); |
| } |
| } |
| } |