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