1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.emergency.widgets.countdown; 18 19 import android.content.Context; 20 import android.graphics.Canvas; 21 import android.graphics.Color; 22 import android.graphics.Paint; 23 import android.graphics.Rect; 24 import android.graphics.RectF; 25 import android.graphics.Typeface; 26 27 import androidx.annotation.GuardedBy; 28 29 import com.android.emergency.R; 30 31 import java.time.Duration; 32 33 /** 34 * The renderer which renders the text view to show how many seconds left to count down. 35 */ 36 public class CountDownRenderer { 37 38 @GuardedBy("this") 39 private final Paint mTimerTextPaint; 40 41 @GuardedBy("this") 42 private final Paint mUnitTextPaint; 43 44 @GuardedBy("this") 45 private RectF mBounds = null; 46 47 @GuardedBy("this") 48 private boolean mIsRevealed = false; 49 50 @GuardedBy("this") 51 private Duration mCountDownLeft; 52 53 private Context mContext; 54 private String mSecondUnitText; 55 private float mUnitTextLeftMargin; 56 CountDownRenderer(Context context)57 CountDownRenderer(Context context) { 58 mContext = context; 59 mTimerTextPaint = new Paint(); 60 mTimerTextPaint.setTextAlign(Paint.Align.CENTER); 61 mTimerTextPaint.setTypeface(Typeface.SANS_SERIF); 62 mTimerTextPaint.setColor(Color.WHITE); 63 mTimerTextPaint.setAntiAlias(true); 64 mUnitTextPaint = new Paint(); 65 mUnitTextPaint.setTypeface(Typeface.SANS_SERIF); 66 mUnitTextPaint.setTextAlign(Paint.Align.CENTER); 67 mUnitTextPaint.setColor(Color.WHITE); 68 mUnitTextPaint.setAntiAlias(true); 69 mSecondUnitText = context.getString(R.string.count_down_unit); 70 } 71 72 /** Shows the count down rendering. */ show()73 synchronized void show() { 74 mIsRevealed = true; 75 } 76 77 /** Returns true if count down text should be displayed, false otherwise. */ isRevealed()78 synchronized boolean isRevealed() { 79 return mIsRevealed; 80 } 81 82 /** 83 * Draws the count down text. 84 * 85 * <p>Called from {@link LoopingAnimationThread}. 86 */ draw(Canvas canvas)87 synchronized void draw(Canvas canvas) { 88 if (!mIsRevealed || mBounds == null || mCountDownLeft == null) { 89 return; 90 } 91 92 String timerText = Long.toString(mCountDownLeft.getSeconds()); 93 RectF timerTextBounds = measureText(timerText, mTimerTextPaint); 94 RectF unitTextBounds = measureText(mSecondUnitText, mUnitTextPaint); 95 // Shift timer text and unit text to share baseline. 96 unitTextBounds.offset(0, timerTextBounds.height() - unitTextBounds.height()); 97 // Calculate final text bound that all text will be drawn inside of. 98 RectF finalTextBounds = 99 new RectF( 100 0, 101 0, 102 timerTextBounds.width() + unitTextBounds.width() + mUnitTextLeftMargin, 103 timerTextBounds.height()); 104 // Offset unit text bounds to right of timer text bounds. 105 unitTextBounds.offset(timerTextBounds.width() + mUnitTextLeftMargin, 0); 106 107 // Center final text bounds in provided bounds and place timer text and unit text inside. 108 finalTextBounds.offset( 109 mBounds.left + (mBounds.width() - finalTextBounds.width()) * 0.5f, 110 mBounds.bottom - (mBounds.height() - finalTextBounds.height())); 111 timerTextBounds.offset(finalTextBounds.left, finalTextBounds.top); 112 unitTextBounds.offset(finalTextBounds.left, finalTextBounds.top); 113 114 // Draw text. 115 canvas.drawText( 116 timerText, timerTextBounds.centerX(), timerTextBounds.bottom, mTimerTextPaint); 117 canvas.drawText( 118 mSecondUnitText, unitTextBounds.centerX(), unitTextBounds.bottom, mUnitTextPaint); 119 } 120 121 /** 122 * Sets the bounds text should be drawn in. 123 * 124 * <p>Called from ui thread. 125 */ updateBounds(RectF loopBounds, float totalDiameter)126 synchronized void updateBounds(RectF loopBounds, float totalDiameter) { 127 this.mBounds = 128 new RectF(loopBounds.left, loopBounds.top, loopBounds.right, loopBounds.bottom); 129 float textSizeToBoundsRatio = 130 mContext.getResources().getFloat( 131 R.dimen.count_down_view_text_size_to_diameter_ratio); 132 float unitTextToTimerTextSizeRatio = 133 mContext.getResources().getFloat( 134 R.dimen.count_down_view_unit_text_to_time_text_size_ratio); 135 float unitTextLeftMarginToBoundsRatio = 136 mContext.getResources().getFloat( 137 R.dimen.count_down_view_unit_text_left_margin_to_diameter_ratio); 138 mTimerTextPaint.setTextSize(textSizeToBoundsRatio * totalDiameter); 139 mUnitTextPaint.setTextSize( 140 textSizeToBoundsRatio * unitTextToTimerTextSizeRatio * totalDiameter); 141 mUnitTextLeftMargin = unitTextLeftMarginToBoundsRatio * totalDiameter; 142 } 143 measureText(String text, Paint textPaint)144 private static RectF measureText(String text, Paint textPaint) { 145 Rect textBounds = new Rect(); 146 textPaint.getTextBounds(text, 0, text.length(), textBounds); 147 RectF textBoundsF = new RectF(textBounds); 148 // Readjust text to be aligned to origin as their top left. 149 textBoundsF.offset(-textBounds.left, -textBounds.top); 150 return textBoundsF; 151 } 152 setCountDownLeft(Duration timeLeft)153 synchronized void setCountDownLeft(Duration timeLeft) { 154 mCountDownLeft = timeLeft; 155 } 156 } 157