package com.ptrprograms.endgamewatchface; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Resources; import android.content.res.TypedArray; 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.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.support.wearable.watchface.CanvasWatchFaceService; import android.support.wearable.watchface.WatchFaceService; import android.support.wearable.watchface.WatchFaceStyle; import android.text.format.Time; import android.util.Log; import android.view.SurfaceHolder; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.wearable.DataApi; import com.google.android.gms.wearable.DataEvent; import com.google.android.gms.wearable.DataEventBuffer; import com.google.android.gms.wearable.DataItem; import com.google.android.gms.wearable.DataMap; import com.google.android.gms.wearable.DataMapItem; import com.google.android.gms.wearable.Wearable; import java.util.TimeZone; import java.util.concurrent.TimeUnit; /** * Created by paulruiz on 3/29/15. */ public class WatchFace extends CanvasWatchFaceService { private static final String SHARED_PREFERENCE_POSITION = "shared_preference_position"; public static final String DATA_LAYER_PATH = "/watchface"; public static final String KEY_BACKGROUND_POSITION = "key_background_position"; public static final String KEY_TIME = "key_time"; public static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1); @Override public Engine onCreateEngine() { return new Engine(); } private class Engine extends CanvasWatchFaceService.Engine implements DataApi.DataListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { private static final int MSG_UPDATE_TIME = 0; private boolean mMute; private boolean mRegisteredTimeZoneReceiver = false; private boolean mLowBitAmbient; private Time mTime; private Bitmap mBackgroundBitmap; private Bitmap mBackgroundScaledBitmap; private Paint mHourPaint; private Paint mMinutePaint; private Paint mSecondPaint; private Paint mTickPaint; /** Handler to update the time once a second in interactive mode. */ final Handler mUpdateTimeHandler = new Handler() { @Override public void handleMessage(Message message) { invalidate(); switch ( message.what ) { case MSG_UPDATE_TIME: if( shouldTimerBeRunning() ) { long timeMs = System.currentTimeMillis(); long delayMs = INTERACTIVE_UPDATE_RATE_MS - (timeMs % INTERACTIVE_UPDATE_RATE_MS); mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs); } break; } } }; final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() { @Override public void onReceive( Context context, Intent intent ) { mTime.clear( intent.getStringExtra( "time-zone" ) ); mTime.setToNow(); } }; GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder( WatchFace.this ) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(Wearable.API) .build(); @Override public void onCreate(SurfaceHolder holder) { super.onCreate(holder); initWatchFaceStyle(); initBackground(); initHourPaint(); initMinutePaint(); initSecondPaint(); initTickPaint(); mTime = new Time(); } private void initWatchFaceStyle() { setWatchFaceStyle( new WatchFaceStyle.Builder( WatchFace.this ) .setCardPeekMode( WatchFaceStyle.PEEK_MODE_SHORT ) .setBackgroundVisibility( WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE ) .setShowSystemUiTime( false ) .build() ); } private void initBackground() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); TypedArray typedArray = getResources().obtainTypedArray( R.array.background_resource_ids ); initBackground( typedArray.getResourceId( preferences.getInt( SHARED_PREFERENCE_POSITION, 0 ), 0 ) ); typedArray.recycle(); } private void initBackground( int resId ) { Resources resources = WatchFace.this.getResources(); Drawable backgroundDrawable = resources.getDrawable( resId ); mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap(); mBackgroundScaledBitmap = null; } private void initHourPaint() { mHourPaint = new Paint(); mHourPaint.setARGB(255, 0, 0, 0); mHourPaint.setStrokeWidth(5.f); mHourPaint.setAntiAlias(true); mHourPaint.setStrokeCap(Paint.Cap.ROUND); } private void initMinutePaint() { mMinutePaint = new Paint(); mMinutePaint.setARGB(255, 0, 0, 0); mMinutePaint.setStrokeWidth(3.f); mMinutePaint.setAntiAlias(true); mMinutePaint.setStrokeCap(Paint.Cap.ROUND); } private void initSecondPaint() { mSecondPaint = new Paint(); mSecondPaint.setARGB(255, 255, 0, 0); mSecondPaint.setStrokeWidth(2.f); mSecondPaint.setAntiAlias(true); mSecondPaint.setStrokeCap(Paint.Cap.ROUND); } private void initTickPaint() { mTickPaint = new Paint(); mTickPaint.setARGB(100, 255, 255, 255); mTickPaint.setStrokeWidth(2.f); mTickPaint.setAntiAlias(true); } @Override public void onDestroy() { mUpdateTimeHandler.removeMessages( MSG_UPDATE_TIME ); if( mRegisteredTimeZoneReceiver ) unregisterReceiver(); if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { Wearable.DataApi.removeListener(mGoogleApiClient, this); mGoogleApiClient.disconnect(); } super.onDestroy(); } @Override public void onPropertiesChanged( Bundle properties ) { super.onPropertiesChanged( properties ); mLowBitAmbient = properties.getBoolean( PROPERTY_LOW_BIT_AMBIENT, false ); } @Override public void onTimeTick() { super.onTimeTick(); invalidate(); } @Override public void onAmbientModeChanged( boolean inAmbientMode ) { super.onAmbientModeChanged( inAmbientMode ); if ( mLowBitAmbient ) { boolean antiAlias = !inAmbientMode; mHourPaint.setAntiAlias( antiAlias ); mMinutePaint.setAntiAlias( antiAlias ); mSecondPaint.setAntiAlias( antiAlias ); mTickPaint.setAntiAlias( antiAlias ); } invalidate(); // Whether the timer should be running depends on whether we're in ambient mode (as well // as whether we're visible), so we may need to start or stop the timer. updateTimer(); } @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 onDraw( Canvas canvas, Rect bounds ) { mTime.setToNow(); int width = bounds.width(); int height = bounds.height(); if ( mBackgroundScaledBitmap == null || mBackgroundScaledBitmap.getWidth() != width || mBackgroundScaledBitmap.getHeight() != height ) { mBackgroundScaledBitmap = Bitmap.createScaledBitmap( mBackgroundBitmap, width, height, true ); } canvas.drawBitmap( mBackgroundScaledBitmap, 0, 0, null ); float centerX = width / 2.0f; float centerY = height / 2.0f; drawWatchTicks( centerX, centerY, canvas ); float secRot = mTime.second / 30f * (float) Math.PI; int minutes = mTime.minute; float minRot = minutes / 30f * (float) Math.PI; float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI; float secLength = centerX - 20; float minLength = centerX - 40; float hrLength = centerX - 80; 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); 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); } } private void drawWatchTicks( float centerX, float centerY, Canvas canvas ) { float innerTickRadius = centerX - 10; 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) * centerX; float outerY = (float) -Math.cos(tickRot) * centerX; canvas.drawLine(centerX + innerX, centerY + innerY, centerX + outerX, centerY + outerY, mTickPaint); } } @Override public void onVisibilityChanged(boolean visible) { super.onVisibilityChanged(visible); if( visible ) { if( mGoogleApiClient != null && !mGoogleApiClient.isConnected() ) mGoogleApiClient.connect(); registerReceiver(); mTime.clear( TimeZone.getDefault().getID() ); mTime.setToNow(); } else { unregisterReceiver(); } updateTimer(); } private void registerReceiver() { if ( mRegisteredTimeZoneReceiver ) { return; } mRegisteredTimeZoneReceiver = true; IntentFilter filter = new IntentFilter( Intent.ACTION_TIMEZONE_CHANGED ); WatchFace.this.registerReceiver( mTimeZoneReceiver, filter ); } private void unregisterReceiver() { if ( !mRegisteredTimeZoneReceiver ) { return; } mRegisteredTimeZoneReceiver = false; WatchFace.this.unregisterReceiver( mTimeZoneReceiver ); } private void updateTimer() { mUpdateTimeHandler.removeMessages( MSG_UPDATE_TIME ); if ( shouldTimerBeRunning() ) { mUpdateTimeHandler.sendEmptyMessage( MSG_UPDATE_TIME ); } } private boolean shouldTimerBeRunning() { return isVisible() && !isInAmbientMode(); } @Override public void onConnected(Bundle bundle) { Wearable.DataApi.addListener(mGoogleApiClient, this); } @Override public void onConnectionSuspended(int i) { } @Override public void onDataChanged(DataEventBuffer dataEvents) { for( DataEvent event : dataEvents ) { if( event.getType() == DataEvent.TYPE_CHANGED ) { DataItem item = event.getDataItem(); if( item.getUri().getPath().compareTo( DATA_LAYER_PATH ) == 0 ) { DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap(); int selectedBackgroundPosition = dataMap.getInt(KEY_BACKGROUND_POSITION); TypedArray typedArray = getResources().obtainTypedArray( R.array.background_resource_ids ); initBackground( typedArray.getResourceId( selectedBackgroundPosition, 0 ) ); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); preferences.edit().putInt( SHARED_PREFERENCE_POSITION, selectedBackgroundPosition ).commit(); typedArray.recycle(); invalidate(); } } } } @Override public void onConnectionFailed(ConnectionResult connectionResult) { } } }