/*
* Author: Balch
* Created: 7/29/16 7:16 AM
*
* This file is part of MockTrade.
*
* MockTrade is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MockTrade is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MockTrade. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2016
*
*/
package com.balch.mocktrade;
import android.animation.ValueAnimator;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LightingColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.graphics.Typeface;
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.Log;
import android.view.Gravity;
import android.view.SurfaceHolder;
import android.view.WindowInsets;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.balch.android.app.framework.types.Money;
import com.balch.mocktrade.shared.HighlightItem;
import com.balch.mocktrade.shared.PerformanceItem;
import com.balch.mocktrade.shared.WatchConfigItem;
import com.balch.mocktrade.shared.WearDataSync;
import com.balch.mocktrade.shared.utils.TextFormatUtils;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
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.DataItemBuffer;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.Wearable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
/**
* Digital watch face with seconds. In ambient mode, the seconds aren't displayed. On devices with
* low-bit ambient mode, the text is drawn without anti-aliasing in ambient mode.
*/
public class MockTradeFace extends CanvasWatchFaceService {
private static final String TAG = MockTradeFace.class.getSimpleName();
private static final float TIME_TICK_STROKE_WIDTH = 3f;
private static final float SHADOW_RADIUS = 6;
private static final int BASE_PERFORMANCE_COLOR_COMPONENT = 156;
private static final int OFF_PERFORMANCE_COLOR_COMPONENT = 128;
private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(15);
private static final int MSG_UPDATE_TIME = 0;
private static final int ANIMATION_DURATION_MS = 500;
private Typeface mTimeFont;
@Override
public Engine onCreateEngine() {
mTimeFont = Typeface.createFromAsset(this.getAssets(), "fonts/lovelyweekend.ttf");
return new Engine();
}
private static class EngineHandler extends Handler {
private final WeakReference<MockTradeFace.Engine> mWeakReference;
public EngineHandler(MockTradeFace.Engine reference) {
mWeakReference = new WeakReference<>(reference);
}
@Override
public void handleMessage(Message msg) {
MockTradeFace.Engine engine = mWeakReference.get();
if (engine != null) {
switch (msg.what) {
case MSG_UPDATE_TIME:
engine.handleUpdateTimeMessage();
break;
}
}
}
}
private class Engine extends CanvasWatchFaceService.Engine implements DataApi.DataListener,
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
final Handler mUpdateTimeHandler = new EngineHandler(this);
boolean mRegisteredTimeZoneReceiver = false;
GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(MockTradeFace.this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Wearable.API)
.build();
private Paint mBackgroundPaint;
private Paint mTextPaint;
private Paint mDayCirclePaint;
private Paint mCurrentTimeDayPaint;
private Paint mMarketTimePaint;
private Paint mMarketDayRingPaint;
private Calendar mTime;
private Rect mTextSizeRect = new Rect();
private float mYOffset;
private int mChinSize;
private float mOuterWidth;
private float mInnerWidth;
private int mOuterColor;
private int mInnerColor;
private RectF mMarketArcRect = new RectF();
private float mMarketOpenDegrees;
private float mMarketDurationDegrees;
private boolean mZoomMarketArc; // if two, outer circle is 12HR
private ArrayList<PerformanceItem> mPerformanceItems;
private float mPerformanceDurationDegrees;
private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mTime.setTimeZone(TimeZone.getDefault());
calcMarketTimes();
calcPerformanceGradient();
}
};
private List<HighlightItem> mHighlightItems;
private int mHighlightItemPosition = 0;
private Paint mHighlightDescTextPaint;
private Paint mHighlightSymbolTextPaint;
private Paint mHighlightDataTextPaint;
private float mHighlightYOffset;
private int mColorPositive;
private int mColorNegative;
private boolean mLowBitAmbient;
private boolean mShowTwentyFourHourTime;
@Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
setWatchFaceStyle(new WatchFaceStyle.Builder(MockTradeFace.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
.setAmbientPeekMode(WatchFaceStyle.AMBIENT_PEEK_MODE_HIDDEN)
.setPeekOpacityMode(WatchFaceStyle.PEEK_OPACITY_MODE_OPAQUE)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setShowSystemUiTime(false)
.setAcceptsTapEvents(true)
.setStatusBarGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP)
.build());
Resources resources = MockTradeFace.this.getResources();
mOuterWidth = resources.getDimension(R.dimen.day_circle_outer_width);
mInnerWidth = resources.getDimension(R.dimen.day_circle_inner_width);
mOuterColor = ContextCompat.getColor(MockTradeFace.this, R.color.day_circle_outer_color);
mInnerColor = ContextCompat.getColor(MockTradeFace.this, R.color.day_circle_inner_color);
mYOffset = resources.getDimension(R.dimen.digital_y_offset);
mYOffset = resources.getDimension(R.dimen.digital_y_offset);
mHighlightYOffset = resources.getDimension(R.dimen.highlight_y_offset);
mBackgroundPaint = new Paint();
mBackgroundPaint.setColor(ContextCompat.getColor(MockTradeFace.this, R.color.background));
mTextPaint = new Paint();
mTextPaint.setColor(ContextCompat.getColor(MockTradeFace.this, R.color.digital_text));
mTextPaint.setTypeface(mTimeFont);
mTextPaint.setAntiAlias(true);
mHighlightDescTextPaint = new Paint();
mHighlightDescTextPaint.setColor(ContextCompat.getColor(MockTradeFace.this, R.color.highlight_text_desc_color));
mHighlightDescTextPaint.setTextSize(resources.getDimensionPixelSize(R.dimen.highlight_desc_size));
mHighlightDescTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
mHighlightDescTextPaint.setAntiAlias(true);
mHighlightSymbolTextPaint = new Paint();
mHighlightSymbolTextPaint.setColor(ContextCompat.getColor(MockTradeFace.this, R.color.highlight_text_symbol_color));
mHighlightSymbolTextPaint.setTextSize(resources.getDimensionPixelSize(R.dimen.highlight_symbol_size));
mHighlightSymbolTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
mHighlightSymbolTextPaint.setAntiAlias(true);
mHighlightDataTextPaint = new Paint();
mHighlightDataTextPaint.setTextSize(resources.getDimensionPixelSize(R.dimen.highlight_data_size));
mHighlightDataTextPaint.setTypeface(Typeface.DEFAULT);
mHighlightDataTextPaint.setAntiAlias(true);
mColorPositive = ContextCompat.getColor(MockTradeFace.this, R.color.data_color_pos);
mColorNegative = ContextCompat.getColor(MockTradeFace.this, R.color.data_color_neg);
mDayCirclePaint = new Paint();
mDayCirclePaint.setStrokeCap(Paint.Cap.ROUND);
mDayCirclePaint.setStyle(Paint.Style.STROKE);
mDayCirclePaint.setAntiAlias(true);
mDayCirclePaint.setStrokeJoin(Paint.Join.MITER);
mCurrentTimeDayPaint = new Paint();
mCurrentTimeDayPaint.setColor(ContextCompat.getColor(MockTradeFace.this, R.color.day_circle_tick_color));
mCurrentTimeDayPaint.setStrokeWidth(TIME_TICK_STROKE_WIDTH);
mCurrentTimeDayPaint.setAntiAlias(true);
mCurrentTimeDayPaint.setStyle(Paint.Style.STROKE);
mCurrentTimeDayPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, Color.BLACK);
mCurrentTimeDayPaint.setStrokeJoin(Paint.Join.BEVEL);
mMarketTimePaint = new Paint();
mMarketTimePaint.setStrokeCap(Paint.Cap.ROUND);
mMarketTimePaint.setStyle(Paint.Style.STROKE);
mMarketTimePaint.setAntiAlias(true);
mMarketTimePaint.setStrokeWidth(mInnerWidth);
mMarketTimePaint.setColor(ContextCompat.getColor(MockTradeFace.this, R.color.market_inner_color));
mMarketTimePaint.setStrokeJoin(Paint.Join.BEVEL);
mMarketDayRingPaint = new Paint();
mMarketDayRingPaint.setStrokeCap(Paint.Cap.ROUND);
mMarketDayRingPaint.setStyle(Paint.Style.STROKE);
mMarketDayRingPaint.setAntiAlias(true);
mMarketDayRingPaint.setStrokeWidth(mOuterWidth);
mMarketDayRingPaint.setStrokeJoin(Paint.Join.BEVEL);
mTime = GregorianCalendar.getInstance();
calcMarketTimes();
}
@Override
public void onDestroy() {
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
super.onDestroy();
}
@Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
if (visible) {
mGoogleApiClient.connect();
registerReceiver();
mTime.setTimeInMillis(System.currentTimeMillis());
} else {
unregisterReceiver();
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
Wearable.DataApi.removeListener(mGoogleApiClient, this);
mGoogleApiClient.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);
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
MockTradeFace.this.registerReceiver(mTimeZoneReceiver, filter);
}
private void unregisterReceiver() {
if (!mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = false;
MockTradeFace.this.unregisterReceiver(mTimeZoneReceiver);
}
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
super.onSurfaceChanged(holder, format, width, height);
sizeChanged();
}
public void onApplyWindowInsets(WindowInsets insets) {
super.onApplyWindowInsets(insets);
mChinSize = insets.getSystemWindowInsetBottom();
// Load resources that have alternate values for round watches.
Resources resources = MockTradeFace.this.getResources();
boolean isRound = insets.isRound();
float textSize = resources.getDimension(isRound
? R.dimen.digital_text_size_round : R.dimen.digital_text_size);
mTextPaint.setTextSize(textSize);
sizeChanged();
}
private void sizeChanged() {
Rect frame = getSurfaceHolder().getSurfaceFrame();
float offset = mChinSize + ((mOuterWidth + (mOuterWidth - mInnerWidth)) / 2.0f);
mMarketArcRect = new RectF(offset, offset, frame.width()-offset, frame.height()-offset);
calcPerformanceGradient();
}
private void calcMarketTimes() {
mMarketOpenDegrees = getDegrees(getMarketOpenTime());
float endDegrees = getDegrees(getMarketCloseTime());
// handle case where endtime is less than start time
if (endDegrees < mMarketOpenDegrees) {
endDegrees += 360;
}
mMarketDurationDegrees = endDegrees - mMarketOpenDegrees;
}
private void calcPerformanceGradient() {
Rect frame = getSurfaceHolder().getSurfaceFrame();
Shader shader = null;
if ((mPerformanceItems != null) && mPerformanceItems.size() > 0) {
float extent = getPerformanceExtent(mPerformanceItems.get(0).getValue().getMicroCents());
Calendar cal = Calendar.getInstance();
int size = mPerformanceItems.size();
int[] colors = new int[size];
float[] positions = new float[size];
cal.setTimeInMillis(mPerformanceItems.get(0).getTimestamp().getTime());
float startDegrees = getDegrees(cal);
for (int x = 0; x < size; x++) {
PerformanceItem item = mPerformanceItems.get(x);
long todayChange = item.getTodayChange().getMicroCents();
// todayChange = (long)(-extents + (extents * 2*x/size));
colors[x] = getPerformanceColor(todayChange, extent);
cal.setTimeInMillis(item.getTimestamp().getTime());
float calDegrees = getDegrees(cal);
if (calDegrees < startDegrees) {
calDegrees += 360;
}
positions[x] = (calDegrees - startDegrees) / 360.0f;
}
shader = new SweepGradient(frame.centerX(), frame.centerY(), colors, positions);
cal.setTimeInMillis(mPerformanceItems.get(size-1).getTimestamp().getTime());
float calDegrees = getDegrees(cal);
if (calDegrees < mMarketOpenDegrees) {
calDegrees += 360;
}
mPerformanceDurationDegrees = calDegrees - mMarketOpenDegrees;
}
mMarketDayRingPaint.setShader(shader);
}
private float getPerformanceExtent(long value) {
return .01f * value / 2.0f;
}
private int getPerformanceColor(long value, float extent) {
int color = Color.rgb(156, 156, 156);
if (value != 0) {
float colorPercent = (Math.abs(value) / extent);
int colorComponent = (int) Math.min(255, BASE_PERFORMANCE_COLOR_COMPONENT + (255 - BASE_PERFORMANCE_COLOR_COMPONENT) * colorPercent);
int secondaryColorComponent = (int) Math.max(0, OFF_PERFORMANCE_COLOR_COMPONENT - (255 - OFF_PERFORMANCE_COLOR_COMPONENT) * colorPercent * 1.5f);
color = (value < 0) ? Color.rgb(colorComponent, secondaryColorComponent, secondaryColorComponent) : Color.rgb(secondaryColorComponent, colorComponent, secondaryColorComponent);
}
return color;
}
@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) {
mTextPaint.setAntiAlias(!inAmbientMode);
mDayCirclePaint.setAntiAlias(!inAmbientMode);
mCurrentTimeDayPaint.setAntiAlias(!inAmbientMode);
mMarketTimePaint.setAntiAlias(!inAmbientMode);
mMarketDayRingPaint.setAntiAlias(!inAmbientMode);
mHighlightDescTextPaint.setAntiAlias(!inAmbientMode);
mHighlightSymbolTextPaint.setAntiAlias(!inAmbientMode);
mHighlightDataTextPaint.setAntiAlias(!inAmbientMode);
}
if (inAmbientMode) {
mTextPaint.setColorFilter(null);
} else {
if ((mHighlightItems != null) && !mHighlightItems.isEmpty()) {
HighlightItem item = mHighlightItems.get(mHighlightItemPosition);
setTimeTextPaint(item, true);
}
}
invalidate();
updateTimer();
}
@Override
public void onTapCommand(int tapType, int x, int y, long eventTime) {
switch (tapType) {
case TAP_TYPE_TOUCH:
break;
case TAP_TYPE_TOUCH_CANCEL:
break;
case TAP_TYPE_TAP:
if (timeTickHitTest(x, y)) {
mZoomMarketArc = !mZoomMarketArc;
calcMarketTimes();
calcPerformanceGradient();
} else if (marketTimeHitTest(x, y, true)) {
if (mHighlightItems != null) {
HighlightItem item = mHighlightItems.get(mHighlightItemPosition);
if (item.isTotalType()) {
startActivity(GraphActivity.newIntent(getApplicationContext(), item, mPerformanceItems));
}
}
} else if (!marketTimeHitTest(x, y, false)) { // did not click in time ring
if (mHighlightItems != null) {
mHighlightItemPosition = ++mHighlightItemPosition % mHighlightItems.size();
HighlightItem item = mHighlightItems.get(mHighlightItemPosition);
setTimeTextPaint(item, false);
setAccountIdDataItem(item);
}
}
invalidate();
break;
}
}
private boolean marketTimeHitTest(int x, int y, boolean inPerformanceArea) {
float circleRadius = mMarketArcRect.height() /2.0f;
float centerX = mMarketArcRect.centerX();
float centerY = mMarketArcRect.centerY();
boolean isHit = false;
//calculate the distance of the touch point from the center of your circle
double dist = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
if (Math.abs(dist - circleRadius) <= mOuterWidth*2) {
double angle = (Math.atan2(y - centerY, x - centerX) * 57.2958) + 90.0f;
if (angle < 0 ){
angle += 360.0;
} else if (angle >= 360.0) {
angle -= 360.0;
}
isHit = ((angle >= mMarketOpenDegrees) && (angle <= mMarketOpenDegrees+mMarketDurationDegrees));
if (!inPerformanceArea) {
isHit = !isHit;
}
}
return isHit;
}
private boolean timeTickHitTest(int x, int y) {
float circleRadius = mMarketArcRect.height() /2.0f;
float centerX = mMarketArcRect.centerX();
float centerY = mMarketArcRect.centerY();
boolean isHit = false;
//calculate the distance of the touch point from the center of your circle
double dist = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
if (Math.abs(dist - circleRadius) <= mOuterWidth*2) {
double angle = (Math.atan2(y - centerY, x - centerX) * 57.2958) + 90.0f;
if (angle < 0 ){
angle += 360.0;
} else if (angle >= 360.0) {
angle -= 360.0;
}
float nowTick = getDegrees(mTime);
isHit = ((angle >= nowTick-5) && (angle <= nowTick+5));
}
return isHit;
}
@Override
public void onDraw(Canvas canvas, Rect bounds) {
int width = bounds.width();
int height = bounds.height();
// Draw the background.
if (isInAmbientMode()) {
canvas.drawColor(Color.BLACK);
} else {
canvas.drawRect(0, 0, width, height, mBackgroundPaint);
}
float centerX = width / 2f;
float centerY = height / 2f;
float radius = Math.min(centerX, centerY) - mChinSize - mDayCirclePaint.getStrokeWidth();
mDayCirclePaint.setStrokeWidth(mOuterWidth);
mDayCirclePaint.setColor(mOuterColor);
canvas.drawCircle(centerX, centerY, radius, mDayCirclePaint);
mDayCirclePaint.setStrokeWidth(mInnerWidth);
mDayCirclePaint.setColor(mInnerColor);
canvas.drawCircle(centerX, centerY, radius, mDayCirclePaint);
mTime.setTimeInMillis(System.currentTimeMillis());
canvas.save();
canvas.rotate(mMarketOpenDegrees - 90, centerX, centerY);
// only draw default arc if the performance arc is not complete
if (mMarketDurationDegrees > mPerformanceDurationDegrees) {
canvas.drawArc(mMarketArcRect, 0, mMarketDurationDegrees, false, mMarketTimePaint);
}
// draw performance arc
if (mPerformanceItems != null) {
canvas.drawArc(mMarketArcRect, 0, mPerformanceDurationDegrees, false, mMarketDayRingPaint);
}
canvas.restore();
drawTimeTick(mTime, centerX, centerY, canvas, mCurrentTimeDayPaint);
int hour = mTime.get(mShowTwentyFourHourTime ? Calendar.HOUR_OF_DAY : Calendar.HOUR);
if (!mShowTwentyFourHourTime && (hour == 0)) {
hour = 12;
}
String formatText = mShowTwentyFourHourTime ? "%02d:%02d" : "%d:%02d";
String text = String.format(formatText, hour, mTime.get(Calendar.MINUTE)) ;
mTextPaint.getTextBounds(text, 0, text.length(), mTextSizeRect);
float x = centerX - mTextSizeRect.width() / 2f - mTextSizeRect.left;
canvas.drawText(text, x, mYOffset, mTextPaint);
if ((mHighlightItems != null) && !mHighlightItems.isEmpty()) {
HighlightItem item = mHighlightItems.get(mHighlightItemPosition);
text = (item.getHighlightType() != HighlightItem.HighlightType.TOTAL_ACCOUNT) ?
item.getDescription() : item.getSymbol();
mHighlightDescTextPaint.getTextBounds(text, 0, text.length(), mTextSizeRect);
x = centerX - mTextSizeRect.width() / 2f - mTextSizeRect.left;
canvas.drawText(text, x, mHighlightYOffset, mHighlightDescTextPaint);
float y = getYOffset(mHighlightYOffset, mTextSizeRect);
if (item.isTotalType()) {
drawAccountHighlightItem(item, centerX, y, canvas);
} else {
drawSymbolHighlightItem(item, centerX, y, canvas);
}
}
}
private float getYOffset(float startValue, Rect textSizeRect) {
return startValue + textSizeRect.height() + 5f;
}
private void drawSymbolHighlightItem(HighlightItem item, float centerX, float y, Canvas canvas) {
String text = item.getSymbol();
mHighlightSymbolTextPaint.getTextBounds(text, 0, text.length(), mTextSizeRect);
float x = centerX - mTextSizeRect.width() / 2f - mTextSizeRect.left;
canvas.drawText(text, x, y, mHighlightSymbolTextPaint);
y = getYOffset(y, mTextSizeRect);
boolean isDayChange = item.isDayChangeType();
Money value = isDayChange ? item.getTodayChange() : item.getTotalChange();
String percent = TextFormatUtils.getPercentString(isDayChange ? item.getTodayChangePercent() : item.getTotalChangePercent());
text = TextFormatUtils.getDollarString(value.getDollars()) + " (" + percent + ")";
mHighlightDataTextPaint.getTextBounds(text, 0, text.length(), mTextSizeRect);
x = centerX - mTextSizeRect.width() / 2f - mTextSizeRect.left;
mHighlightDataTextPaint.setColor((value.getMicroCents() < 0) ? mColorNegative : mColorPositive);
canvas.drawText(text, x, y, mHighlightDataTextPaint);
}
private void drawAccountHighlightItem(HighlightItem item, float centerX, float y, Canvas canvas) {
String text = item.getValue().getFormatted(0);
mHighlightDataTextPaint.getTextBounds(text, 0, text.length(), mTextSizeRect);
float x = centerX - mTextSizeRect.width() / 2f - mTextSizeRect.left;
mHighlightDataTextPaint.setColor(Color.WHITE);
canvas.drawText(text, x, y, mHighlightDataTextPaint);
y = getYOffset(y, mTextSizeRect);
Money value = item.getTodayChange();
int percent = -1;
if (value.getMicroCents() == 0) {
value = item.getTotalChange();
percent = (int) item.getTotalChangePercent();
}
text = TextFormatUtils.getDollarString(value.getDollars());
if (percent != -1) {
text += " (" + TextFormatUtils.getPercentString(percent) + ")";
}
mHighlightDataTextPaint.getTextBounds(text, 0, text.length(), mTextSizeRect);
x = centerX - mTextSizeRect.width() / 2f - mTextSizeRect.left;
mHighlightDataTextPaint.setColor((value.getMicroCents() < 0) ? mColorNegative : mColorPositive);
canvas.drawText(text, x, y, mHighlightDataTextPaint);
}
private void drawTimeTick(Calendar time, float centerX, float centerY, Canvas canvas, Paint paint) {
float hours = getHours(time);
float innerTickRadius = centerX - 13 - mChinSize;
float outerTickRadius = centerX - 2 - mChinSize;
float tickRot = (float) (hours * Math.PI * 2 / getRingIncrements());
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, paint);
}
private float getHours(Calendar time) {
return time.get(Calendar.HOUR_OF_DAY) + time.get(Calendar.MINUTE) / 60.0f + time.get(Calendar.SECOND) / 3600.0f;
}
private int getRingIncrements() {
return mZoomMarketArc ? 12 : 24;
}
private float getDegrees(Calendar time) {
float hours = time.get(Calendar.HOUR_OF_DAY) + time.get(Calendar.MINUTE) / 60.0f + time.get(Calendar.SECOND) / 3600.0f;
return (hours / getRingIncrements() * 360.0f) % 360;
}
/**
* 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);
}
}
private void updateDataItemAndUiOnStartup() {
PendingResult<DataItemBuffer> results = Wearable.DataApi.getDataItems(mGoogleApiClient);
results.setResultCallback(new ResultCallback<DataItemBuffer>() {
@Override
public void onResult(DataItemBuffer dataItems) {
for (int x = 0; x < dataItems.getCount(); x++) {
updateFromDataMap(dataItems.get(x));
}
if ((mHighlightItems != null) && !mHighlightItems.isEmpty()) {
setTimeTextPaint(mHighlightItems.get(mHighlightItemPosition), false);
}
dataItems.release();
invalidate();
}
});
}
private void updateFromDataMap(DataItem dataItem) {
DataMapItem dataMapItem = DataMapItem.fromDataItem(dataItem);
DataMap dataMap = dataMapItem.getDataMap();
String uriPath = dataItem.getUri().getPath();
if (uriPath.equals(WearDataSync.PATH_SNAPSHOT_SYNC)) {
updatePathSnapshotSync(dataMap);
} else if (uriPath.equals(WearDataSync.PATH_HIGHLIGHTS_SYNC)) {
updatePathHighlightsSync(dataMap);
} else if (uriPath.equals(WearDataSync.PATH_WATCH_CONFIG_SYNC)) {
updatePathWatchConfigSync(dataMap);
}
}
private void updatePathWatchConfigSync(DataMap dataMap) {
ArrayList<DataMap> dataMapList = dataMap.getDataMapArrayList(WearDataSync.DATA_WATCH_CONFIG_DATA_ITEMS);
if (dataMapList != null) {
for (DataMap dm : dataMapList) {
WatchConfigItem item = new WatchConfigItem(dm);
if (item.getKey().equals("pref_twenty_four_hour_display")) {
mShowTwentyFourHourTime = item.isEnabled();
}
}
}
}
private void updatePathHighlightsSync(DataMap dataMap) {
ArrayList<DataMap> dataMapList = dataMap.getDataMapArrayList(WearDataSync.DATA_HIGHLIGHTS);
if (dataMapList != null) {
mHighlightItems = new ArrayList<>(dataMapList.size());
for (DataMap data : dataMapList) {
HighlightItem item = new HighlightItem(data);
mHighlightItems.add(item);
if (item.getHighlightType() == HighlightItem.HighlightType.TOTAL_ACCOUNT) {
setTimeTextPaint(item, false);
}
}
if (mHighlightItemPosition >= mHighlightItems.size()) {
mHighlightItemPosition = 0;
}
} else {
mHighlightItems = null;
mHighlightItemPosition = 0;
}
}
private void updatePathSnapshotSync(DataMap dataMap) {
ArrayList<DataMap> dataMapList = dataMap.getDataMapArrayList(WearDataSync.DATA_SNAPSHOT_DAILY);
if (dataMapList != null) {
mPerformanceItems = new ArrayList<>(dataMapList.size());
for (DataMap data : dataMapList) {
PerformanceItem item = new PerformanceItem(data);
mPerformanceItems.add(item);
}
if (mPerformanceItems.size() == 0) {
mPerformanceItems = null;
}
} else {
mPerformanceItems = null;
}
calcPerformanceGradient();
}
private void setTimeTextPaint(HighlightItem item, boolean animate) {
if (item.isTotalType()) {
final float extent = getPerformanceExtent(item.getValue().getMicroCents());
final long value = (item.getTodayChange().getMicroCents() != 0) ?
item.getTodayChange().getMicroCents() :
item.getTotalChange().getMicroCents();
if (animate) {
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.setDuration(ANIMATION_DURATION_MS);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animationPercent = (float) animation.getAnimatedValue();
setPerformancePaint((long) (value * animationPercent), extent);
}
});
animator.start();
} else {
setPerformancePaint(value, extent);
}
} else {
mTextPaint.setColorFilter(null);
}
}
private void setPerformancePaint(long value, float extent) {
int color = getPerformanceColor(value , extent);
mTextPaint.setColorFilter(new LightingColorFilter(color, Color.argb(28, 128, 126, 128)));
invalidate();
}
private void setAccountIdDataItem(HighlightItem item) {
long accountId = -1;
if (item.getHighlightType() == HighlightItem.HighlightType.TOTAL_ACCOUNT) {
accountId = item.getAccountId();
}
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(WearDataSync.PATH_WATCH_FACE_ACCOUNT_ID);
putDataMapRequest.getDataMap().putLong(WearDataSync.DATA_WATCH_FACE_ACCOUNT_ID, accountId);
putDataMapRequest.setUrgent();
Wearable.DataApi.putDataItem(mGoogleApiClient, putDataMapRequest.asPutDataRequest());
}
private Calendar getMarketOpenTime() {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"));
cal.set(Calendar.HOUR_OF_DAY, 6);
cal.set(Calendar.MINUTE, 30);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
Calendar cal2 = Calendar.getInstance();
cal2.setTimeInMillis(cal.getTimeInMillis());
return cal2;
}
private Calendar getMarketCloseTime() {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"));
cal.set(Calendar.HOUR_OF_DAY, 13);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
Calendar cal2 = Calendar.getInstance();
cal2.setTimeInMillis(cal.getTimeInMillis());
return cal2;
}
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
Log.d(TAG, "onDataChanged: ");
for (DataEvent dataEvent : dataEvents) {
if (dataEvent.getType() != DataEvent.TYPE_CHANGED) {
continue;
}
DataItem dataItem = dataEvent.getDataItem();
updateFromDataMap(dataItem);
}
invalidate();
}
@Override
public void onConnected(Bundle connectionHint) {
Log.d(TAG, "onConnected: ");
Wearable.DataApi.addListener(mGoogleApiClient, Engine.this);
updateDataItemAndUiOnStartup();
}
@Override
public void onConnectionSuspended(int cause) {
Log.d(TAG, "onConnectionSuspended: " + cause);
}
@Override
public void onConnectionFailed(ConnectionResult result) {
Log.e(TAG, "onConnectionFailed: " + result);
}
}
}