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