TimerItem.java revision 51917c54f72faa32d2cb3c287de3d0816b4f9017
16d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux/* 26d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Copyright (C) 2015 The Android Open Source Project 36d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * 46d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Licensed under the Apache License, Version 2.0 (the "License"); 56d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * you may not use this file except in compliance with the License. 66d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * You may obtain a copy of the License at 76d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * 86d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * http://www.apache.org/licenses/LICENSE-2.0 96d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * 106d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Unless required by applicable law or agreed to in writing, software 116d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * distributed under the License is distributed on an "AS IS" BASIS, 126d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * See the License for the specific language governing permissions and 146d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * limitations under the License. 156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuxpackage com.android.deskclock.timer; 186d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.content.Context; 2087f4b763f206cca755e7ce03baf3ef33c33be29eSean Stoutimport android.content.res.ColorStateList; 2187f4b763f206cca755e7ce03baf3ef33c33be29eSean Stoutimport android.content.res.TypedArray; 2287f4b763f206cca755e7ce03baf3ef33c33be29eSean Stoutimport android.graphics.Color; 236d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.os.SystemClock; 2487f4b763f206cca755e7ce03baf3ef33c33be29eSean Stoutimport android.support.v4.view.ViewCompat; 256d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.text.TextUtils; 266d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.util.AttributeSet; 2788969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stoutimport android.widget.Button; 286d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.widget.LinearLayout; 296d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.widget.TextView; 306d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 316d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport com.android.deskclock.R; 3251917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassenimport com.android.deskclock.ThemeUtils; 330cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stoutimport com.android.deskclock.TimerTextController; 3487f4b763f206cca755e7ce03baf3ef33c33be29eSean Stoutimport com.android.deskclock.Utils.ClickAccessibilityDelegate; 356d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport com.android.deskclock.data.Timer; 366d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 3751917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassenimport static android.R.attr.state_activated; 3851917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassenimport static android.R.attr.state_pressed; 3951917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen 406d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux/** 416d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * This view is a visual representation of a {@link Timer}. 426d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 43c624a3fb698c13312a5e14114c37f45e3b3438bcJustin Klaassenpublic class TimerItem extends LinearLayout { 446d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 456d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** Displays the remaining time or time since expiration. */ 460cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stout private TextView mTimerText; 470cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stout 48d4d902b892ea79862d57605f071bde43630a356eSean Stout /** Formats and displays the text in the timer. */ 490cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stout private TimerTextController mTimerTextController; 506d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 516d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** Displays timer progress as a color circle that changes from white to red. */ 526d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private TimerCircleView mCircleView; 536d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** A button that either resets the timer or adds time to it, depending on its state. */ 5588969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout private Button mResetAddButton; 566d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 576d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** Displays the label associated with the timer. Tapping it presents an edit dialog. */ 586d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private TextView mLabelView; 596d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 606d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** The last state of the timer that was rendered; used to avoid expensive operations. */ 616d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private Timer.State mLastState; 626d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 636d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public TimerItem(Context context) { 646d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux this(context, null); 656d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 666d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 676d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public TimerItem(Context context, AttributeSet attrs) { 686d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux super(context, attrs); 696d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 706d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 716d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux @Override 726d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux protected void onFinishInflate() { 736d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux super.onFinishInflate(); 746d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mLabelView = (TextView) findViewById(R.id.timer_label); 7588969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout mResetAddButton = (Button) findViewById(R.id.reset_add); 766d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mCircleView = (TimerCircleView) findViewById(R.id.timer_time); 770cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stout mTimerText = (TextView) findViewById(R.id.timer_time_text); 780cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stout mTimerTextController = new TimerTextController(mTimerText); 7987f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout 8051917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen final Context c = mTimerText.getContext(); 8151917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen final int colorAccent = ThemeUtils.resolveColor(c, R.attr.colorAccent); 8251917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen final int textColorPrimary = ThemeUtils.resolveColor(c, android.R.attr.textColorPrimary); 8351917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen mTimerText.setTextColor(new ColorStateList( 8451917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen new int[][] { { ~state_activated, ~state_pressed }, {} }, 8551917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen new int[] { textColorPrimary, colorAccent })); 866d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 876d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 886d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 896d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Updates this view to display the latest state of the {@code timer}. 906d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 916d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux void update(Timer timer) { 926d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux // Update the time. 930cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stout mTimerTextController.setTimeString(timer.getRemainingTime()); 946d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 956d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux // Update the label if it changed. 966d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux final String label = timer.getLabel(); 976d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux if (!TextUtils.equals(label, mLabelView.getText())) { 986d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mLabelView.setText(label); 996d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1006d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1016d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux // Update visibility of things that may blink. 1026d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux final boolean blinkOff = SystemClock.elapsedRealtime() % 1000 < 500; 10367646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux if (mCircleView != null) { 1042a07ae3286fd5c76f71546890e0f02af99065825Sean Stout final boolean hideCircle = (timer.isExpired() || timer.isMissed()) && blinkOff; 10567646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux mCircleView.setVisibility(hideCircle ? INVISIBLE : VISIBLE); 10667646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux 10767646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux if (!hideCircle) { 10867646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux // Update the progress of the circle. 10967646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux mCircleView.update(timer); 11067646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux } 11167646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux } 11287f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout if (!timer.isPaused() || !blinkOff || mTimerText.isPressed()) { 11387f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setAlpha(1f); 114b53fa89fbcbb4a301d1c67c0f9849bff6ded32caSean Stout } else { 11587f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setAlpha(0f); 116b53fa89fbcbb4a301d1c67c0f9849bff6ded32caSean Stout } 1176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1186d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux // Update some potentially expensive areas of the user interface only on state changes. 1196d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux if (timer.getState() != mLastState) { 1206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mLastState = timer.getState(); 12187f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout final Context context = getContext(); 1226d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux switch (mLastState) { 12388969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout case RESET: 1246d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux case PAUSED: { 12588969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout mResetAddButton.setText(R.string.timer_reset); 126d6e128a5116486e47264bb80eb61a0a10dfcf9bcSean Stout mResetAddButton.setContentDescription(null); 12787f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setClickable(true); 12887f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setActivated(false); 12987f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 13087f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout ViewCompat.setAccessibilityDelegate(mTimerText, new ClickAccessibilityDelegate( 13187f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout context.getString(R.string.timer_start), true)); 13287f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout break; 13387f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout } 13487f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout case RUNNING: { 13587f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout final String addTimeDesc = context.getString(R.string.timer_plus_one); 13687f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mResetAddButton.setText(R.string.timer_add_minute); 13787f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mResetAddButton.setContentDescription(addTimeDesc); 13887f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setClickable(true); 13987f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setActivated(false); 14087f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 14187f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout ViewCompat.setAccessibilityDelegate(mTimerText, new ClickAccessibilityDelegate( 14287f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout context.getString(R.string.timer_pause))); 1436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux break; 1446d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 14588969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout case EXPIRED: 14688969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout case MISSED: { 14787f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout final String addTimeDesc = context.getString(R.string.timer_plus_one); 14888969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout mResetAddButton.setText(R.string.timer_add_minute); 1496d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mResetAddButton.setContentDescription(addTimeDesc); 15087f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setClickable(false); 15187f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setActivated(true); 15287f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 1536d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux break; 1546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1556d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1566d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1576d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 15851917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen} 159