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; 216d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.os.SystemClock; 2287f4b763f206cca755e7ce03baf3ef33c33be29eSean Stoutimport android.support.v4.view.ViewCompat; 236d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.text.TextUtils; 246d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.util.AttributeSet; 2588969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stoutimport android.widget.Button; 266d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.widget.LinearLayout; 276d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport android.widget.TextView; 286d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 296d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport com.android.deskclock.R; 3051917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassenimport com.android.deskclock.ThemeUtils; 310cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stoutimport com.android.deskclock.TimerTextController; 3287f4b763f206cca755e7ce03baf3ef33c33be29eSean Stoutimport com.android.deskclock.Utils.ClickAccessibilityDelegate; 336d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieuximport com.android.deskclock.data.Timer; 346d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 3551917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassenimport static android.R.attr.state_activated; 3651917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassenimport static android.R.attr.state_pressed; 3751917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen 386d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux/** 396d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * This view is a visual representation of a {@link Timer}. 406d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 41c624a3fb698c13312a5e14114c37f45e3b3438bcJustin Klaassenpublic class TimerItem extends LinearLayout { 426d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 436d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** Displays the remaining time or time since expiration. */ 440cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stout private TextView mTimerText; 450cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stout 46d4d902b892ea79862d57605f071bde43630a356eSean Stout /** Formats and displays the text in the timer. */ 470cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stout private TimerTextController mTimerTextController; 486d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 496d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** Displays timer progress as a color circle that changes from white to red. */ 506d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private TimerCircleView mCircleView; 516d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 526d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** A button that either resets the timer or adds time to it, depending on its state. */ 5388969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout private Button mResetAddButton; 546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 556d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** Displays the label associated with the timer. Tapping it presents an edit dialog. */ 566d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private TextView mLabelView; 576d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 586d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** The last state of the timer that was rendered; used to avoid expensive operations. */ 596d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux private Timer.State mLastState; 606d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 616d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public TimerItem(Context context) { 626d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux this(context, null); 636d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 646d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 656d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux public TimerItem(Context context, AttributeSet attrs) { 666d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux super(context, attrs); 676d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 686d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 696d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux @Override 706d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux protected void onFinishInflate() { 716d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux super.onFinishInflate(); 726d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mLabelView = (TextView) findViewById(R.id.timer_label); 7388969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout mResetAddButton = (Button) findViewById(R.id.reset_add); 746d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mCircleView = (TimerCircleView) findViewById(R.id.timer_time); 750cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stout mTimerText = (TextView) findViewById(R.id.timer_time_text); 760cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stout mTimerTextController = new TimerTextController(mTimerText); 7787f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout 7851917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen final Context c = mTimerText.getContext(); 7951917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen final int colorAccent = ThemeUtils.resolveColor(c, R.attr.colorAccent); 8051917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen final int textColorPrimary = ThemeUtils.resolveColor(c, android.R.attr.textColorPrimary); 8151917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen mTimerText.setTextColor(new ColorStateList( 825ea8dcc77ded62f1fb7195eae100522a87ebf705Sean Stout new int[][] { { -state_activated, -state_pressed }, {} }, 8351917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen new int[] { textColorPrimary, colorAccent })); 846d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 856d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 866d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux /** 876d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux * Updates this view to display the latest state of the {@code timer}. 886d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux */ 896d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux void update(Timer timer) { 906d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux // Update the time. 910cdf9bb0d4b3519049276133c04adc62bb7be23eSean Stout mTimerTextController.setTimeString(timer.getRemainingTime()); 926d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 936d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux // Update the label if it changed. 946d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux final String label = timer.getLabel(); 956d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux if (!TextUtils.equals(label, mLabelView.getText())) { 966d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mLabelView.setText(label); 976d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 986d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 996d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux // Update visibility of things that may blink. 1006d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux final boolean blinkOff = SystemClock.elapsedRealtime() % 1000 < 500; 10167646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux if (mCircleView != null) { 1022a07ae3286fd5c76f71546890e0f02af99065825Sean Stout final boolean hideCircle = (timer.isExpired() || timer.isMissed()) && blinkOff; 10367646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux mCircleView.setVisibility(hideCircle ? INVISIBLE : VISIBLE); 10467646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux 10567646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux if (!hideCircle) { 10667646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux // Update the progress of the circle. 10767646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux mCircleView.update(timer); 10867646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux } 10967646b22a4e9dd8ab9a8ef4a8bde82f793d969a3James Lemieux } 11087f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout if (!timer.isPaused() || !blinkOff || mTimerText.isPressed()) { 11187f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setAlpha(1f); 112b53fa89fbcbb4a301d1c67c0f9849bff6ded32caSean Stout } else { 11387f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setAlpha(0f); 114b53fa89fbcbb4a301d1c67c0f9849bff6ded32caSean Stout } 1156d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux 1166d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux // Update some potentially expensive areas of the user interface only on state changes. 1176d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux if (timer.getState() != mLastState) { 1186d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mLastState = timer.getState(); 11987f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout final Context context = getContext(); 1206d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux switch (mLastState) { 12188969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout case RESET: 1226d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux case PAUSED: { 12388969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout mResetAddButton.setText(R.string.timer_reset); 124d6e128a5116486e47264bb80eb61a0a10dfcf9bcSean Stout mResetAddButton.setContentDescription(null); 12587f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setClickable(true); 12687f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setActivated(false); 12787f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 12887f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout ViewCompat.setAccessibilityDelegate(mTimerText, new ClickAccessibilityDelegate( 12987f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout context.getString(R.string.timer_start), true)); 13087f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout break; 13187f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout } 13287f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout case RUNNING: { 13387f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout final String addTimeDesc = context.getString(R.string.timer_plus_one); 13487f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mResetAddButton.setText(R.string.timer_add_minute); 13587f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mResetAddButton.setContentDescription(addTimeDesc); 13687f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setClickable(true); 13787f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setActivated(false); 13887f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 13987f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout ViewCompat.setAccessibilityDelegate(mTimerText, new ClickAccessibilityDelegate( 14087f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout context.getString(R.string.timer_pause))); 1416d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux break; 1426d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 14388969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout case EXPIRED: 14488969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout case MISSED: { 14587f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout final String addTimeDesc = context.getString(R.string.timer_plus_one); 14688969aecfc735f88d37cb767aa23779abd8c5ad9Sean Stout mResetAddButton.setText(R.string.timer_add_minute); 1476d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux mResetAddButton.setContentDescription(addTimeDesc); 14887f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setClickable(false); 14987f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setActivated(true); 15087f4b763f206cca755e7ce03baf3ef33c33be29eSean Stout mTimerText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 1516d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux break; 1526d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1536d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1546d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 1556d603b7c62bb38d763a681a8bf20fadb1442e833James Lemieux } 15651917c54f72faa32d2cb3c287de3d0816b4f9017Justin Klaassen} 157