195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung/*
295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung * Copyright (C) 2013 The Android Open Source Project
395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung *
495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung * Licensed under the Apache License, Version 2.0 (the "License");
595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung * you may not use this file except in compliance with the License.
695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung * You may obtain a copy of the License at
795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung *
895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung *      http://www.apache.org/licenses/LICENSE-2.0
995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung *
1095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung * Unless required by applicable law or agreed to in writing, software
1195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung * distributed under the License is distributed on an "AS IS" BASIS,
1295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung * See the License for the specific language governing permissions and
1495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung * limitations under the License
1595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung */
1695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungpackage com.android.deskclock;
1795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
1895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.content.ContentResolver;
1995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.content.Context;
2095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.content.res.Resources;
2195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.database.ContentObserver;
2295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.graphics.Canvas;
2395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.graphics.Color;
2495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.graphics.Paint;
2595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.graphics.Paint.Align;
2695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.net.Uri;
2795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.os.AsyncTask;
2895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.os.Handler;
2995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.text.format.DateFormat;
3095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.text.format.DateUtils;
3195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.util.AttributeSet;
3295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport android.view.View;
3395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
3495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport com.android.deskclock.provider.Alarm;
3595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
3695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport java.text.SimpleDateFormat;
3795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport java.util.Calendar;
3895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport java.util.Date;
3995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport java.util.HashSet;
4095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport java.util.Iterator;
4195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport java.util.List;
4295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport java.util.Locale;
4395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungimport java.util.TreeMap;
4495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
4595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung/**
4695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung * Renders a tree-like view of the next alarm times over the period of a week.
4795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung * The timeline begins at the time of the next alarm, and ends a week after that time.
4895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung * The view is currently only shown in the landscape mode of tablets.
4995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung */
5095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kungpublic class AlarmTimelineView extends View {
5195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
5295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private static final String TAG = "AlarmTimelineView";
5395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
5495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private static final String FORMAT_12_HOUR = "E h mm a";
5595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private static final String FORMAT_24_HOUR = "E H mm";
5695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
5795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private static final int DAYS_IN_WEEK = 7;
5895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
5995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private int mAlarmTimelineColor;
6095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private int mAlarmTimelineLength;
6195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private int mAlarmTimelineMarginTop;
6295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private int mAlarmTimelineMarginBottom;
6395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private int mAlarmNodeRadius;
6495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private int mAlarmNodeInnerRadius;
6595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private int mAlarmNodeInnerRadiusColor;
6695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private int mAlarmTextPadding;
6795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private int mAlarmTextSize;
6895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private int mAlarmMinDistance;
6995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
7095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private Paint mPaint;
7195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private ContentResolver mResolver;
7295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private SimpleDateFormat mDateFormat;
7395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private TreeMap<Date, AlarmTimeNode> mAlarmTimes = new TreeMap<Date, AlarmTimeNode>();
7495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private Calendar mCalendar;
7595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private AlarmObserver mAlarmObserver = new AlarmObserver(getHandler());
7695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private GetAlarmsTask mAlarmsTask = new GetAlarmsTask();
77f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung    private String mNoAlarmsScheduled;
78f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung    private boolean mIsAnimatingOut;
7995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
8095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    /**
8195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung     * Observer for any changes to the alarms in the content provider.
8295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung     */
8395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private class AlarmObserver extends ContentObserver {
8495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
8595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        public AlarmObserver(Handler handler) {
8695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            super(handler);
8795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        }
8895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
8995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        @Override
9095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        public void onChange(boolean changed) {
9195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            if (mAlarmsTask != null) {
9295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                mAlarmsTask.cancel(true);
9395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            }
9495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            mAlarmsTask = new GetAlarmsTask();
9595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            mAlarmsTask.execute();
9695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        }
9795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
9895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        @Override
9995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        public void onChange(boolean changed, Uri uri) {
10095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            onChange(changed);
10195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        }
10295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    }
10395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
10495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    /**
10595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung     * The data model for one node on the timeline.
10695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung     */
10795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private class AlarmTimeNode {
10895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        public Date date;
10995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        public boolean isRepeating;
11095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
11195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        public AlarmTimeNode(Date date, boolean isRepeating) {
11295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            this.date = date;
11395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            this.isRepeating = isRepeating;
11495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        }
11595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    }
11695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
11795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    /**
11895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung     * Retrieves alarms from the content provider and generates an alarm node tree sorted by date.
11995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung     */
12095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private class GetAlarmsTask extends AsyncTask<Void, Void, Void> {
12195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
12295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        @Override
12395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        protected synchronized Void doInBackground(Void... params) {
1241c7788b33dd4516dae81e6bcab043addc45fc1a1Paul Sliwowski            List<Alarm> enabledAlarmList = Alarm.getAlarms(mResolver, Alarm.ENABLED + "=1");
12595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            final Date currentTime = mCalendar.getTime();
12695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            mAlarmTimes.clear();
12795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            for (Alarm alarm : enabledAlarmList) {
12895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                int hour = alarm.hour;
12995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                int minutes = alarm.minutes;
13095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                HashSet<Integer> repeatingDays = alarm.daysOfWeek.getSetDays();
13195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
13295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                // If the alarm is not repeating,
13395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                if (repeatingDays.isEmpty()) {
13495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    mCalendar.add(Calendar.DATE, getDaysFromNow(hour, minutes));
13595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    mCalendar.set(Calendar.HOUR_OF_DAY, alarm.hour);
13695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    mCalendar.set(Calendar.MINUTE, alarm.minutes);
13795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    Date date = mCalendar.getTime();
13895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
13995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    if (!mAlarmTimes.containsKey(date)) {
14095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                        // Add alarm if there is no other alarm with this date.
14195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                        mAlarmTimes.put(date, new AlarmTimeNode(date, false));
14295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    }
14395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    mCalendar.setTime(currentTime);
14495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    continue;
14595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                }
14695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
14795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                // If the alarm is repeating, iterate through each alarm date.
14895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                for (int day : alarm.daysOfWeek.getSetDays()) {
14995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    mCalendar.add(Calendar.DATE, getDaysFromNow(day, hour, minutes));
15095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    mCalendar.set(Calendar.HOUR_OF_DAY, alarm.hour);
15195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    mCalendar.set(Calendar.MINUTE, alarm.minutes);
15295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    Date date = mCalendar.getTime();
15395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
15495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    if (!mAlarmTimes.containsKey(date)) {
15595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                        // Add alarm if there is no other alarm with this date.
15695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                        mAlarmTimes.put(date, new AlarmTimeNode(mCalendar.getTime(), true));
15795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    } else {
15895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                        // If there is another alarm with this date, make it
15995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                        // repeating.
16095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                        mAlarmTimes.get(date).isRepeating = true;
16195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    }
16295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    mCalendar.setTime(currentTime);
16395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                }
16495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            }
16595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            return null;
16695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        }
16795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
16895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        @Override
16995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        protected void onPostExecute(Void result) {
170f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung            requestLayout();
17195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            AlarmTimelineView.this.invalidate();
17295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        }
17395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
17495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        // Returns whether this non-repeating alarm is firing today or tomorrow.
17595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        private int getDaysFromNow(int hour, int minutes) {
17695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            final int currentHour = mCalendar.get(Calendar.HOUR_OF_DAY);
17795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            if (hour > currentHour ||
17895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    (hour == currentHour && minutes >= mCalendar.get(Calendar.MINUTE)) ) {
17995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                return 0;
18095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            }
18195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            return 1;
18295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        }
18395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
18495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        // Returns the days from now of the next instance of this alarm, given the repeated day.
18595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        private int getDaysFromNow(int day, int hour, int minute) {
18695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            final int currentDay = mCalendar.get(Calendar.DAY_OF_WEEK);
18795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            if (day != currentDay) {
18895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                if (day < currentDay) {
18995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                    day += DAYS_IN_WEEK;
19095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                }
19195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                return day - currentDay;
19295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            }
19395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
19495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            final int currentHour = mCalendar.get(Calendar.HOUR_OF_DAY);
19595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            if (hour != currentHour) {
19695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                return (hour < currentHour) ? DAYS_IN_WEEK : 0;
19795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            }
19895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
19995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            final int currentMinute = mCalendar.get(Calendar.MINUTE);
20095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            return (minute < currentMinute) ? DAYS_IN_WEEK : 0;
20195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        }
20295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    }
20395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
20495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    public AlarmTimelineView(Context context) {
20595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        super(context);
20695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        init(context);
20795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    }
20895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
20995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    public AlarmTimelineView(Context context, AttributeSet attrs) {
21095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        super(context, attrs);
21195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        init(context);
21295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    }
21395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
21495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private void init(Context context) {
21595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mResolver = context.getContentResolver();
21695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
21795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        final Resources res = context.getResources();
21895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
21995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mAlarmTimelineColor = res.getColor(R.color.alarm_timeline_color);
22095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mAlarmTimelineLength = res.getDimensionPixelOffset(R.dimen.alarm_timeline_length);
22195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mAlarmTimelineMarginTop = res.getDimensionPixelOffset(R.dimen.alarm_timeline_margin_top);
22229b68ab8b8d6ed4e5150735bdc9fa861eca2df8aSam Blitzstein        mAlarmTimelineMarginBottom = res.getDimensionPixelOffset(R.dimen.footer_button_size) +
22329b68ab8b8d6ed4e5150735bdc9fa861eca2df8aSam Blitzstein                2 * res.getDimensionPixelOffset(R.dimen.footer_button_layout_margin);
22495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mAlarmNodeRadius = res.getDimensionPixelOffset(R.dimen.alarm_timeline_radius);
22595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mAlarmNodeInnerRadius = res.getDimensionPixelOffset(R.dimen.alarm_timeline_inner_radius);
22695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mAlarmNodeInnerRadiusColor = res.getColor(R.color.blackish);
22795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mAlarmTextSize = res.getDimensionPixelOffset(R.dimen.alarm_text_font_size);
22895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mAlarmTextPadding = res.getDimensionPixelOffset(R.dimen.alarm_text_padding);
22995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mAlarmMinDistance = res.getDimensionPixelOffset(R.dimen.alarm_min_distance) +
23095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                2 * mAlarmNodeRadius;
231f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        mNoAlarmsScheduled = context.getString(R.string.no_upcoming_alarms);
23295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
23395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mPaint = new Paint();
23495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mPaint.setTextSize(mAlarmTextSize);
23595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mPaint.setStrokeWidth(res.getDimensionPixelOffset(R.dimen.alarm_timeline_width));
23695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mPaint.setAntiAlias(true);
23795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
23895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mCalendar = Calendar.getInstance();
23995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        final Locale locale = Locale.getDefault();
2401c7788b33dd4516dae81e6bcab043addc45fc1a1Paul Sliwowski        String formatString = DateFormat.is24HourFormat(context) ? FORMAT_24_HOUR : FORMAT_12_HOUR;
24195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        String format = DateFormat.getBestDateTimePattern(locale, formatString);
24295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mDateFormat = new SimpleDateFormat(format, locale);
243f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung
24495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mAlarmsTask.execute();
24595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    }
24695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
24795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    @Override
24895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    public void onAttachedToWindow() {
24995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        super.onAttachedToWindow();
25095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mResolver.registerContentObserver(Alarm.CONTENT_URI, true, mAlarmObserver);
25195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    }
25295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
25395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    @Override
25495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    public void onDetachedFromWindow() {
25595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        super.onDetachedFromWindow();
25695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mResolver.unregisterContentObserver(mAlarmObserver);
25795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    }
25895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
25995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    @Override
26095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
26195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
262f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        int timelineHeight = !mAlarmTimes.isEmpty() ?  mAlarmTimelineLength : 0;
26395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
264f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung                timelineHeight + mAlarmTimelineMarginTop + mAlarmTimelineMarginBottom);
265f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung    }
26695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
26795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    @Override
26895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    public synchronized void onDraw(Canvas canvas) {
26995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
270f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        // If the view is in the process of animating out, do not change the text or the timeline.
271f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        if (mIsAnimatingOut) {
27295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            return;
27395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        }
27495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
275f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        super.onDraw(canvas);
276f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung
27795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        final int x = getWidth() / 2;
278f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        int y = mAlarmTimelineMarginTop;
27995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
28095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        mPaint.setColor(mAlarmTimelineColor);
28195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
282f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        // If there are no alarms, draw the no alarms text.
283f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        if (mAlarmTimes == null || mAlarmTimes.isEmpty()) {
284f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung            mPaint.setTextAlign(Align.CENTER);
285f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung            canvas.drawText(mNoAlarmsScheduled, x, y, mPaint);
286f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung            return;
287f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        }
288f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung
289f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        // Draw the timeline.
290f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        canvas.drawLine(x, y, x, y + mAlarmTimelineLength, mPaint);
291f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung
292f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        final int xLeft = x - mAlarmNodeRadius - mAlarmTextPadding;
293f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        final int xRight = x + mAlarmNodeRadius + mAlarmTextPadding;
29495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
29595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        // Iterate through each of the alarm times chronologically.
29695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        Iterator<AlarmTimeNode> iter = mAlarmTimes.values().iterator();
297f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        Date firstDate = null;
298f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        int prevY = 0;
29995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        int i=0;
30095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        final int maxY = mAlarmTimelineLength + mAlarmTimelineMarginTop;
30195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        while (iter.hasNext()) {
30295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            AlarmTimeNode node = iter.next();
30395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            Date date = node.date;
30495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
30595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            if (firstDate == null) {
30695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                // If this is the first alarm, set the node to the top of the timeline.
30795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                y = mAlarmTimelineMarginTop;
30895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                firstDate = date;
30995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            } else {
31095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                // If this is not the first alarm, set the distance based upon the time from the
31195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                // first alarm.  If a node already exists at that time, use the minimum distance
31295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                // required from the last drawn node.
31395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                y = Math.max(convertToDistance(date, firstDate), prevY + mAlarmMinDistance);
31495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            }
31595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
31695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            if (y > maxY) {
31795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                // If the y value has somehow exceeded the timeline length, draw node on end of
31895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                // timeline.  We should never reach this state.
31995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                Log.wtf("Y-value exceeded timeline length.  Should never happen.");
32095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                Log.wtf("alarm date=" + node.date.getTime() + ", isRepeating=" + node.isRepeating
32195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                        + ", y=" + y + ", maxY=" + maxY);
32295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                y = maxY;
32395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            }
32495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
32595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            // Draw the node.
32695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            mPaint.setColor(Color.WHITE);
32795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            canvas.drawCircle(x, y, mAlarmNodeRadius, mPaint);
32895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
32995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            // If the node is not repeating, draw an inner circle to make the node "open".
33095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            if (!node.isRepeating) {
33195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                mPaint.setColor(mAlarmNodeInnerRadiusColor);
33295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                canvas.drawCircle(x, y, mAlarmNodeInnerRadius, mPaint);
33395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            }
33495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            prevY = y;
33595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
33695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            // Draw the alarm text.  Alternate left and right of the timeline.
33795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            final String timeString = mDateFormat.format(date).toUpperCase();
33895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            mPaint.setColor(mAlarmTimelineColor);
33995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            if (i % 2 == 0) {
34095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                mPaint.setTextAlign(Align.RIGHT);
34195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                canvas.drawText(timeString, xLeft, y + mAlarmTextSize / 3, mPaint);
34295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            } else {
34395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                mPaint.setTextAlign(Align.LEFT);
34495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                canvas.drawText(timeString, xRight, y + mAlarmTextSize / 3, mPaint);
34595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            }
34695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            i++;
34795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        }
34895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    }
34995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung
350f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung    // This method is necessary to ensure that the view does not re-draw while it is being
351f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung    // animated out.  The timeline should remain on-screen as is, even though no alarms
352f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung    // are present, as the view moves off-screen.
353f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung    public void setIsAnimatingOut(boolean animatingOut) {
354f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung        mIsAnimatingOut = animatingOut;
355f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung    }
356f7de8003e56d6d6f3dd35f494162c75acc993cd9James Kung
35795db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    // Convert the time difference between the date and the first date to a distance along the
35895db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    // timeline.
35995db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    private int convertToDistance(final Date date, final Date firstDate) {
36095db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        if (date == null || firstDate == null) {
36195db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung            return 0;
36295db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        }
36395db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung        return (int) ((date.getTime() - firstDate.getTime())
36495db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung                * mAlarmTimelineLength / DateUtils.WEEK_IN_MILLIS + mAlarmTimelineMarginTop);
36595db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung    }
36695db8fb4954e768cc58eb71bc3c05dfeef375cd7James Kung}
367