Home | History | Annotate | Download | only in agenda
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.calendar.agenda;
     18 
     19 import com.android.calendar.CalendarController;
     20 import com.android.calendar.CalendarController.EventType;
     21 import com.android.calendar.DeleteEventHelper;
     22 import com.android.calendar.R;
     23 import com.android.calendar.Utils;
     24 import com.android.calendar.agenda.AgendaAdapter.ViewHolder;
     25 import com.android.calendar.agenda.AgendaWindowAdapter.DayAdapterInfo;
     26 import com.android.calendar.agenda.AgendaWindowAdapter.EventInfo;
     27 
     28 import android.content.Context;
     29 import android.graphics.Rect;
     30 import android.os.Handler;
     31 import android.provider.CalendarContract.Attendees;
     32 import android.text.format.Time;
     33 import android.util.AttributeSet;
     34 import android.util.Log;
     35 import android.view.View;
     36 import android.widget.AdapterView;
     37 import android.widget.AdapterView.OnItemClickListener;
     38 import android.widget.ListView;
     39 import android.widget.TextView;
     40 
     41 public class AgendaListView extends ListView implements OnItemClickListener {
     42 
     43     private static final String TAG = "AgendaListView";
     44     private static final boolean DEBUG = false;
     45     private static final int EVENT_UPDATE_TIME = 300000;  // 5 minutes
     46 
     47     private AgendaWindowAdapter mWindowAdapter;
     48     private DeleteEventHelper mDeleteEventHelper;
     49     private Context mContext;
     50     private String mTimeZone;
     51     private Time mTime;
     52     private boolean mShowEventDetailsWithAgenda;
     53     private Handler mHandler = null;
     54 
     55     private final Runnable mTZUpdater = new Runnable() {
     56         @Override
     57         public void run() {
     58             mTimeZone = Utils.getTimeZone(mContext, this);
     59             mTime.switchTimezone(mTimeZone);
     60         }
     61     };
     62 
     63     // runs every midnight and refreshes the view in order to update the past/present
     64     // separator
     65     private final Runnable mMidnightUpdater = new Runnable() {
     66         @Override
     67         public void run() {
     68             refresh(true);
     69             Utils.setMidnightUpdater(mHandler, mMidnightUpdater, mTimeZone);
     70         }
     71     };
     72 
     73     // Runs every EVENT_UPDATE_TIME to gray out past events
     74     private final Runnable mPastEventUpdater = new Runnable() {
     75         @Override
     76         public void run() {
     77             if (updatePastEvents() == true) {
     78                 refresh(true);
     79             }
     80             setPastEventsUpdater();
     81         }
     82     };
     83 
     84     public AgendaListView(Context context, AttributeSet attrs) {
     85         super(context, attrs);
     86         initView(context);
     87     }
     88 
     89     private void initView(Context context) {
     90         mContext = context;
     91         mTimeZone = Utils.getTimeZone(context, mTZUpdater);
     92         mTime = new Time(mTimeZone);
     93         setOnItemClickListener(this);
     94         setVerticalScrollBarEnabled(false);
     95         mWindowAdapter = new AgendaWindowAdapter(context, this,
     96                 Utils.getConfigBool(context, R.bool.show_event_details_with_agenda));
     97         mWindowAdapter.setSelectedInstanceId(-1/* TODO:instanceId */);
     98         setAdapter(mWindowAdapter);
     99         setCacheColorHint(context.getResources().getColor(R.color.agenda_item_not_selected));
    100         mDeleteEventHelper =
    101                 new DeleteEventHelper(context, null, false /* don't exit when done */);
    102         mShowEventDetailsWithAgenda = Utils.getConfigBool(mContext,
    103                 R.bool.show_event_details_with_agenda);
    104         // Hide ListView dividers, they are done in the item views themselves
    105         setDivider(null);
    106         setDividerHeight(0);
    107 
    108         mHandler = new Handler();
    109     }
    110 
    111     // Sets a thread to run every EVENT_UPDATE_TIME in order to update the list
    112     // with grayed out past events
    113     private void setPastEventsUpdater() {
    114 
    115         // Run the thread in the nearest rounded EVENT_UPDATE_TIME
    116         long now = System.currentTimeMillis();
    117         long roundedTime = (now / EVENT_UPDATE_TIME) * EVENT_UPDATE_TIME;
    118         mHandler.removeCallbacks(mPastEventUpdater);
    119         mHandler.postDelayed(mPastEventUpdater, EVENT_UPDATE_TIME - (now - roundedTime));
    120     }
    121 
    122     // Stop the past events thread
    123     private void resetPastEventsUpdater() {
    124         mHandler.removeCallbacks(mPastEventUpdater);
    125     }
    126 
    127     // Go over all visible views and checks if all past events are grayed out.
    128     // Returns true is there is at least one event that ended and it is not
    129     // grayed out.
    130     private boolean updatePastEvents() {
    131 
    132         int childCount = getChildCount();
    133         boolean needUpdate = false;
    134         long now = System.currentTimeMillis();
    135         Time time = new Time(mTimeZone);
    136         time.set(now);
    137         int todayJulianDay = Time.getJulianDay(now, time.gmtoff);
    138 
    139         // Go over views in list
    140         for (int i = 0; i < childCount; ++i) {
    141             View listItem = getChildAt(i);
    142             Object o = listItem.getTag();
    143             if (o instanceof AgendaByDayAdapter.ViewHolder) {
    144                 // day view - check if day in the past and not grayed yet
    145                 AgendaByDayAdapter.ViewHolder holder = (AgendaByDayAdapter.ViewHolder) o;
    146                 if (holder.julianDay <= todayJulianDay && !holder.grayed) {
    147                     needUpdate = true;
    148                     break;
    149                 }
    150             } else if (o instanceof AgendaAdapter.ViewHolder) {
    151                 // meeting view - check if event in the past or started already and not grayed yet
    152                 // All day meetings for a day are grayed out
    153                 AgendaAdapter.ViewHolder holder = (AgendaAdapter.ViewHolder) o;
    154                 if (!holder.grayed && ((!holder.allDay && holder.startTimeMilli <= now) ||
    155                         (holder.allDay && holder.julianDay <= todayJulianDay))) {
    156                     needUpdate = true;
    157                     break;
    158                 }
    159             }
    160         }
    161         return needUpdate;
    162     }
    163 
    164     @Override
    165     protected void onDetachedFromWindow() {
    166         super.onDetachedFromWindow();
    167         mWindowAdapter.close();
    168     }
    169 
    170     // Implementation of the interface OnItemClickListener
    171     @Override
    172     public void onItemClick(AdapterView<?> a, View v, int position, long id) {
    173         if (id != -1) {
    174             // Switch to the EventInfo view
    175             EventInfo event = mWindowAdapter.getEventByPosition(position);
    176             long oldInstanceId = mWindowAdapter.getSelectedInstanceId();
    177             mWindowAdapter.setSelectedView(v);
    178 
    179             // If events are shown to the side of the agenda list , do nothing
    180             // when the same
    181             // event is selected , otherwise show the selected event.
    182 
    183             if (event != null && (oldInstanceId != mWindowAdapter.getSelectedInstanceId() ||
    184                     !mShowEventDetailsWithAgenda)) {
    185                 long startTime = event.begin;
    186                 long endTime = event.end;
    187                 if (event.allDay) {
    188                     startTime = Utils.convertAlldayLocalToUTC(mTime, startTime, mTimeZone);
    189                     endTime = Utils.convertAlldayLocalToUTC(mTime, endTime, mTimeZone);
    190                 }
    191                 mTime.set(startTime);
    192                 CalendarController controller = CalendarController.getInstance(mContext);
    193                 controller.sendEventRelatedEventWithExtra(this, EventType.VIEW_EVENT, event.id,
    194                         startTime, endTime, 0, 0, CalendarController.EventInfo.buildViewExtraLong(
    195                                 Attendees.ATTENDEE_STATUS_NONE, event.allDay),
    196                         controller.getTime());
    197             }
    198         }
    199     }
    200 
    201     public void goTo(Time time, long id, String searchQuery, boolean forced,
    202             boolean refreshEventInfo) {
    203         if (time == null) {
    204             time = mTime;
    205             long goToTime = getFirstVisibleTime();
    206             if (goToTime <= 0) {
    207                 goToTime = System.currentTimeMillis();
    208             }
    209             time.set(goToTime);
    210         }
    211         mTime.set(time);
    212         mTime.switchTimezone(mTimeZone);
    213         mTime.normalize(true);
    214         if (DEBUG) {
    215             Log.d(TAG, "Goto with time " + mTime.toString());
    216         }
    217         mWindowAdapter.refresh(mTime, id, searchQuery, forced, refreshEventInfo);
    218     }
    219 
    220     public void refresh(boolean forced) {
    221         mWindowAdapter.refresh(mTime, -1, null, forced, false);
    222     }
    223 
    224     public void deleteSelectedEvent() {
    225         int position = getSelectedItemPosition();
    226         EventInfo event = mWindowAdapter.getEventByPosition(position);
    227         if (event != null) {
    228             mDeleteEventHelper.delete(event.begin, event.end, event.id, -1);
    229         }
    230     }
    231 
    232     public View getFirstVisibleView() {
    233         Rect r = new Rect();
    234         int childCount = getChildCount();
    235         for (int i = 0; i < childCount; ++i) {
    236             View listItem = getChildAt(i);
    237             listItem.getLocalVisibleRect(r);
    238             if (r.top >= 0) { // if visible
    239                 return listItem;
    240             }
    241         }
    242         return null;
    243     }
    244 
    245     public long getSelectedTime() {
    246         int position = getSelectedItemPosition();
    247         if (position >= 0) {
    248             EventInfo event = mWindowAdapter.getEventByPosition(position);
    249             if (event != null) {
    250                 return event.begin;
    251             }
    252         }
    253         return getFirstVisibleTime();
    254     }
    255 
    256     public AgendaAdapter.ViewHolder getSelectedViewHolder() {
    257         return mWindowAdapter.getSelectedViewHolder();
    258     }
    259 
    260     public long getFirstVisibleTime() {
    261         int position = getFirstVisiblePosition();
    262         if (DEBUG) {
    263             Log.v(TAG, "getFirstVisiblePosition = " + position);
    264         }
    265 
    266         // mShowEventDetailsWithAgenda == true implies we have a sticky header. In that case
    267         // we may need to take the second visible position, since the first one maybe the one
    268         // under the sticky header.
    269         if (mShowEventDetailsWithAgenda) {
    270             View v = getFirstVisibleView ();
    271             if (v != null) {
    272                 Rect r = new Rect ();
    273                 v.getLocalVisibleRect(r);
    274                 if (r.bottom - r.top <=  mWindowAdapter.getStickyHeaderHeight()) {
    275                     position ++;
    276                 }
    277             }
    278         }
    279 
    280         EventInfo event = mWindowAdapter.getEventByPosition(position,
    281                 false /* startDay = date separator date instead of actual event startday */);
    282         if (event != null) {
    283             Time t = new Time(mTimeZone);
    284             t.set(event.begin);
    285             // Save and restore the time since setJulianDay sets the time to 00:00:00
    286             int hour = t.hour;
    287             int minute = t.minute;
    288             int second = t.second;
    289             t.setJulianDay(event.startDay);
    290             t.hour = hour;
    291             t.minute = minute;
    292             t.second = second;
    293             if (DEBUG) {
    294                 t.normalize(true);
    295                 Log.d(TAG, "position " + position + " had time " + t.toString());
    296             }
    297             return t.normalize(false);
    298         }
    299         return 0;
    300     }
    301 
    302     public int getJulianDayFromPosition(int position) {
    303         DayAdapterInfo info = mWindowAdapter.getAdapterInfoByPosition(position);
    304         if (info != null) {
    305             return info.dayAdapter.findJulianDayFromPosition(position - info.offset);
    306         }
    307         return 0;
    308     }
    309 
    310     // Finds is a specific event (defined by start time and id) is visible
    311     public boolean isEventVisible(Time startTime, long id) {
    312 
    313         if (id == -1 || startTime == null) {
    314             return false;
    315         }
    316 
    317         View child = getChildAt(0);
    318         // View not set yet, so not child - return
    319         if (child == null) {
    320             return false;
    321         }
    322         int start = getPositionForView(child);
    323         long milliTime = startTime.toMillis(true);
    324         int childCount = getChildCount();
    325         int eventsInAdapter = mWindowAdapter.getCount();
    326 
    327         for (int i = 0; i < childCount; i++) {
    328             if (i + start >= eventsInAdapter) {
    329                 break;
    330             }
    331             EventInfo event = mWindowAdapter.getEventByPosition(i + start);
    332             if (event == null) {
    333                 continue;
    334             }
    335             if (event.id == id && event.begin == milliTime) {
    336                 View listItem = getChildAt(i);
    337                 if (listItem.getTop() <= getHeight() &&
    338                         listItem.getTop() >= mWindowAdapter.getStickyHeaderHeight()) {
    339                     return true;
    340                 }
    341             }
    342         }
    343         return false;
    344     }
    345 
    346     public long getSelectedInstanceId() {
    347         return mWindowAdapter.getSelectedInstanceId();
    348     }
    349 
    350     public void setSelectedInstanceId(long id) {
    351         mWindowAdapter.setSelectedInstanceId(id);
    352     }
    353 
    354     // Move the currently selected or visible focus down by offset amount.
    355     // offset could be negative.
    356     public void shiftSelection(int offset) {
    357         shiftPosition(offset);
    358         int position = getSelectedItemPosition();
    359         if (position != INVALID_POSITION) {
    360             setSelectionFromTop(position + offset, 0);
    361         }
    362     }
    363 
    364     private void shiftPosition(int offset) {
    365         if (DEBUG) {
    366             Log.v(TAG, "Shifting position " + offset);
    367         }
    368 
    369         View firstVisibleItem = getFirstVisibleView();
    370 
    371         if (firstVisibleItem != null) {
    372             Rect r = new Rect();
    373             firstVisibleItem.getLocalVisibleRect(r);
    374             // if r.top is < 0, getChildAt(0) and getFirstVisiblePosition() is
    375             // returning an item above the first visible item.
    376             int position = getPositionForView(firstVisibleItem);
    377             setSelectionFromTop(position + offset, r.top > 0 ? -r.top : r.top);
    378             if (DEBUG) {
    379                 if (firstVisibleItem.getTag() instanceof AgendaAdapter.ViewHolder) {
    380                     ViewHolder viewHolder = (AgendaAdapter.ViewHolder) firstVisibleItem.getTag();
    381                     Log.v(TAG, "Shifting from " + position + " by " + offset + ". Title "
    382                             + viewHolder.title.getText());
    383                 } else if (firstVisibleItem.getTag() instanceof AgendaByDayAdapter.ViewHolder) {
    384                     AgendaByDayAdapter.ViewHolder viewHolder =
    385                             (AgendaByDayAdapter.ViewHolder) firstVisibleItem.getTag();
    386                     Log.v(TAG, "Shifting from " + position + " by " + offset + ". Date  "
    387                             + viewHolder.dateView.getText());
    388                 } else if (firstVisibleItem instanceof TextView) {
    389                     Log.v(TAG, "Shifting: Looking at header here. " + getSelectedItemPosition());
    390                 }
    391             }
    392         } else if (getSelectedItemPosition() >= 0) {
    393             if (DEBUG) {
    394                 Log.v(TAG, "Shifting selection from " + getSelectedItemPosition() +
    395                         " by " + offset);
    396             }
    397             setSelection(getSelectedItemPosition() + offset);
    398         }
    399     }
    400 
    401     public void setHideDeclinedEvents(boolean hideDeclined) {
    402         mWindowAdapter.setHideDeclinedEvents(hideDeclined);
    403     }
    404 
    405     public void onResume() {
    406         mTZUpdater.run();
    407         Utils.setMidnightUpdater(mHandler, mMidnightUpdater, mTimeZone);
    408         setPastEventsUpdater();
    409         mWindowAdapter.onResume();
    410     }
    411 
    412     public void onPause() {
    413         Utils.resetMidnightUpdater(mHandler, mMidnightUpdater);
    414         resetPastEventsUpdater();
    415     }
    416 }
    417