2 * Copyright (C) 2011 Morphoss Ltd
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 package com
.morphoss
.acal
.activity
;
21 import java
.io
.FileNotFoundException
;
22 import java
.io
.FileOutputStream
;
23 import java
.io
.IOException
;
24 import java
.io
.ObjectOutputStream
;
25 import java
.util
.ArrayList
;
27 import android
.content
.ComponentName
;
28 import android
.content
.Context
;
29 import android
.content
.Intent
;
30 import android
.content
.ServiceConnection
;
31 import android
.os
.Bundle
;
32 import android
.os
.Handler
;
33 import android
.os
.IBinder
;
34 import android
.os
.Message
;
35 import android
.os
.RemoteException
;
36 import android
.util
.Log
;
37 import android
.view
.GestureDetector
;
38 import android
.view
.GestureDetector
.OnGestureListener
;
39 import android
.view
.LayoutInflater
;
40 import android
.view
.Menu
;
41 import android
.view
.MenuInflater
;
42 import android
.view
.MenuItem
;
43 import android
.view
.MotionEvent
;
44 import android
.view
.View
;
45 import android
.view
.View
.OnClickListener
;
46 import android
.view
.View
.OnTouchListener
;
47 import android
.view
.animation
.Animation
;
48 import android
.view
.animation
.AnimationUtils
;
49 import android
.widget
.Button
;
50 import android
.widget
.GridView
;
51 import android
.widget
.ListView
;
52 import android
.widget
.TextView
;
54 import com
.morphoss
.acal
.AcalTheme
;
55 import com
.morphoss
.acal
.Constants
;
56 import com
.morphoss
.acal
.R
;
57 import com
.morphoss
.acal
.acaltime
.AcalDateTime
;
58 import com
.morphoss
.acal
.dataservice
.CalendarDataService
;
59 import com
.morphoss
.acal
.dataservice
.DataRequest
;
60 import com
.morphoss
.acal
.dataservice
.DataRequestCallBack
;
61 import com
.morphoss
.acal
.davacal
.AcalEvent
;
62 import com
.morphoss
.acal
.davacal
.SimpleAcalEvent
;
63 import com
.morphoss
.acal
.service
.aCalService
;
64 import com
.morphoss
.acal
.weekview
.WeekViewActivity
;
65 import com
.morphoss
.acal
.widget
.AcalViewFlipper
;
70 * <h3>This is the activity that is started when aCal is run and will likely be
71 * the most used interface in the program.</h3>
74 * This view is split into 3 sections:
77 * <li>Month View - A grid view controlled by a View Flipper displaying all the
78 * days of a calendar month.</li>
79 * <li>Event View - A grid view controlled by a View Flipper displaying all the
80 * events for the current selected day</li>
81 * <li>Buttons - A set of buttons at the bottom of the screen</li>
84 * As well as this, there is a menu accessible through the menu button.
88 * Each of the view flippers listens to gestures, Side swipes on either will
89 * result in the content of the flipper moving forward or back. Content for the
90 * flippers is provided by Adapter classes that contain the data the view is
95 * At any time there are 2 important pieces of information that make up this
96 * views state: The currently selected day, which is highlighted when visible in
97 * the month view and determines which events are visible in the event list. The
98 * other is the currently displayed date, which determines which month we are
99 * looking at in the month view. This state information is written to and read
100 * from file when the view loses and gains focus.
104 * @author Morphoss Ltd
105 * @license GPL v3 or later
108 public class MonthView
extends AcalActivity
implements OnGestureListener
,
109 OnTouchListener
, OnClickListener
{
111 public static final String TAG
= "aCal MonthView";
113 /** The file that we save state information to */
114 public static final String STATE_FILE
= "/data/data/com.morphoss.acal/monthview.dat";
116 private boolean invokedFromView
= false;
118 /* Fields relating to the Month View: */
120 /** The flipper for the month view */
121 private AcalViewFlipper monthGridFlipper
;
122 /** The root view containing the GridView Object for the Month View */
123 private View monthGridRoot
;
124 /** The GridView object that displays the month */
125 private GridView monthGrid
= null;
126 /** The TextView that displays which month we are looking at */
127 private TextView monthTitle
;
129 /* Fields relating to the Event View */
131 /** The flipper for the Event View */
132 private AcalViewFlipper listViewFlipper
;
133 /** The root view containing the ListView Object for the Event View */
134 private View eventListRoot
;
135 /** The ListView object that displays the Event View */
136 private ListView eventList
= null;
137 /** The TextView that displays which day we are looking at */
138 private TextView eventListTitle
;
139 /** The current event list adapter */
140 private EventListAdapter eventListAdapter
;
142 /* Fields relating to state */
144 /** The month that our Month View should display */
145 private AcalDateTime displayedMonth
;
146 /** The day that our Event View should display */
147 private AcalDateTime selectedDate
;
149 /* Fields relating to buttons */
150 public static final int TODAY
= 0;
151 public static final int WEEK
= 1;
152 public static final int YEAR
= 2;
153 public static final int ADD
= 3;
155 /* Fields Relating to Gesture Detection */
156 private GestureDetector gestureDetector
;
157 private double consumedX
;
158 private double consumedY
;
159 private static final int maxAngleDev
= 30;
160 private static final int minDistance
= 60;
162 /* Fields relating to calendar data */
163 private DataRequest dataRequest
= null;
165 /* Fields relating to Intent Results */
166 public static final int PICK_MONTH_FROM_YEAR_VIEW
= 0;
167 public static final int PICK_TODAY_FROM_EVENT_VIEW
= 1;
168 public static final int PICK_DAY_FROM_WEEK_VIEW
= 2;
171 Animation leftIn
= null;
172 Animation leftOut
= null;
173 Animation rightIn
= null;
174 Animation rightOut
= null;
176 private int eventListIndex
= 0;
177 private int eventListTop
= 0;
179 /********************************************************
180 * Activity Overrides *
181 ********************************************************/
185 * Called when Activity is first created. Initialises all appropriate fields
186 * and Constructs the Views for display.
191 * @see android.app.Activity#onCreate(android.os.Bundle)
194 public void onCreate(Bundle savedInstanceState
) {
195 super.onCreate(savedInstanceState
);
196 this.setContentView(R
.layout
.month_view
);
198 Bundle b
= this.getIntent().getExtras();
199 if ( b
!= null && b
.containsKey("InvokedFromView") )
200 invokedFromView
= true;
202 // make sure aCalService is running
203 this.startService(new Intent(this, aCalService
.class));
205 gestureDetector
= new GestureDetector(this);
208 this.setupButton(R
.id
.month_today_button
, TODAY
, getString(R
.string
.Today
));
209 this.setupButton(R
.id
.month_week_button
, WEEK
, getString(R
.string
.Week
));
210 this.setupButton(R
.id
.month_year_button
, YEAR
, getString(R
.string
.Year
));
211 this.setupButton(R
.id
.month_add_button
, ADD
, "+");
213 AcalDateTime currentDate
= new AcalDateTime().applyLocalTimeZone();
214 selectedDate
= currentDate
.clone();
215 displayedMonth
= currentDate
;
217 leftIn
= AnimationUtils
.loadAnimation(this, R
.anim
.push_left_in
);
218 leftOut
= AnimationUtils
.loadAnimation(this, R
.anim
.push_left_out
);
219 rightIn
= AnimationUtils
.loadAnimation(this, R
.anim
.push_right_in
);
220 rightOut
= AnimationUtils
.loadAnimation(this, R
.anim
.push_right_out
);
224 private void connectToService() {
226 Log
.v(TAG
,TAG
+ " - Connecting to service with dataRequest ="+(dataRequest
== null?
"null" : "non-null"));
227 Intent intent
= new Intent(this, CalendarDataService
.class);
228 Bundle b
= new Bundle();
229 b
.putInt(CalendarDataService
.BIND_KEY
,
230 CalendarDataService
.BIND_DATA_REQUEST
);
232 this.bindService(intent
, mConnection
, Context
.BIND_AUTO_CREATE
);
234 catch (Exception e
) {
235 Log
.e(TAG
, "Error connecting to service: " + e
.getMessage());
240 private synchronized void serviceIsConnected() {
241 if ( this.monthGrid
== null ) createGridView(true);
242 changeSelectedDate(selectedDate
);
243 changeDisplayedMonth(displayedMonth
);
246 private synchronized void serviceIsDisconnected() {
247 this.dataRequest
= null;
252 * Called when Activity regains focus. Try's to load the saved State.
257 * @see android.app.Activity#onResume()
260 public void onResume() {
262 Log
.v(TAG
,TAG
+ " - onResume");
264 selectedDate
= new AcalDateTime().applyLocalTimeZone();
265 displayedMonth
= new AcalDateTime().applyLocalTimeZone();
266 if ( prefs
.getLong(getString(R
.string
.prefSavedSelectedDate
), 0) > (System
.currentTimeMillis() - (3600000L * 6))) {
267 selectedDate
.setMillis(prefs
.getLong(getString(R
.string
.prefSelectedDate
), System
.currentTimeMillis()));
268 displayedMonth
.setMillis(prefs
.getLong(getString(R
.string
.prefDisplayedMonth
), System
.currentTimeMillis()));
270 selectedDate
.setDaySecond(0);
271 displayedMonth
.setDaySecond(0).setMonthDay(1);
278 * Called when activity loses focus or is closed. Try's to save the current
284 * @see android.app.Activity#onPause()
287 public void onPause() {
290 rememberCurrentPosition();
293 if (dataRequest
!= null) {
294 dataRequest
.flushCache();
295 dataRequest
.unregisterCallback(mCallback
);
297 this.unbindService(mConnection
);
299 catch (RemoteException re
) { }
300 catch (IllegalArgumentException e
) { }
306 prefs
.edit().putLong(getString(R
.string
.prefSavedSelectedDate
), System
.currentTimeMillis()).commit();
307 prefs
.edit().putLong(getString(R
.string
.prefSelectedDate
), selectedDate
.getMillis()).commit();
308 prefs
.edit().putLong(getString(R
.string
.prefDisplayedMonth
), displayedMonth
.getMillis()).commit();
312 private void rememberCurrentPosition() {
313 // save index and top position
314 if ( eventList
!= null ) {
315 eventListIndex
= eventList
.getFirstVisiblePosition();
316 View v
= eventList
.getChildAt(0);
317 eventListTop
= (v
== null) ?
0 : v
.getTop();
318 if ( Constants
.LOG_DEBUG
) Log
.println(Constants
.LOGD
, TAG
,
319 "Saved list view position of "+eventListIndex
+", "+eventListTop
);
324 private void restoreCurrentPosition() {
325 if ( eventList
!= null ) {
326 eventList
.setSelectionFromTop(eventListIndex
, eventListTop
);
328 if ( Constants
.LOG_DEBUG
) Log
.println(Constants
.LOGD
, TAG
,
329 "Set list view to position "+eventListIndex
+", "+eventListTop
);
332 /****************************************************
334 ****************************************************/
338 * Helper method for setting up buttons
342 private void setupButton(int id
, int val
, String buttonLabel
) {
343 Button myButton
= (Button
) this.findViewById(id
);
344 if (myButton
== null) {
345 Log
.i(TAG
, "Cannot find button '" + id
+ "' by ID, to set value '" + val
+ "'");
348 myButton
.setText(buttonLabel
);
349 myButton
.setOnClickListener(this);
350 myButton
.setTag(val
);
351 AcalTheme
.setContainerFromTheme(myButton
, AcalTheme
.BUTTON
);
357 * Creates a new GridView object based on this Activities current state. The
358 * GridView created Will display this Activities MonthView
363 * Whether or not to set the ViewFlipper as the new GridView's
364 * Parent. if set to false the caller is contracted to add a
365 * parent to the GridView.
368 private void createGridView(boolean addParent
) {
371 LayoutInflater inflater
= (LayoutInflater
) this
372 .getSystemService(Context
.LAYOUT_INFLATER_SERVICE
);
374 // Get grid flipper and add month grid
375 monthGridFlipper
= (AcalViewFlipper
) findViewById(R
.id
.month_grid_flipper
);
376 monthGridFlipper
.setAnimationCacheEnabled(true);
378 // Add parent if directed to do so
379 monthGridRoot
= inflater
.inflate(R
.layout
.month_grid_view
, (addParent?monthGridFlipper
:null));
382 monthTitle
= (TextView
) monthGridRoot
383 .findViewById(R
.id
.month_grid_title
);
385 monthGrid
= (GridView
) monthGridRoot
386 .findViewById(R
.id
.month_default_gridview
);
387 monthGrid
.setSelector(R
.drawable
.no_border
);
388 monthGrid
.setOnTouchListener(this);
389 } catch (Exception e
) {
390 Log
.e(TAG
, "Error occured creating gridview: " + e
.getMessage());
396 * Creates a new GridView object based on this Activities current state. The
397 * GridView created will display this Activities ListView
402 * Whether or not to set the ViewFlipper as the new GridView's
403 * Parent. if set to false the caller is contracted to add a
404 * parent to the GridView.
407 private void createListView(boolean addParent
) {
409 LayoutInflater inflater
= (LayoutInflater
) this.getSystemService(Context
.LAYOUT_INFLATER_SERVICE
);
411 // Get List Flipper and add list
412 listViewFlipper
= (AcalViewFlipper
) findViewById(R
.id
.month_list_flipper
);
413 listViewFlipper
.setAnimationCacheEnabled(true);
415 // Add parent if directed to do so
417 eventListRoot
= inflater
.inflate(R
.layout
.month_list_view
,
420 eventListRoot
= inflater
.inflate(R
.layout
.month_list_view
, null);
423 eventListTitle
= (TextView
) eventListRoot
.findViewById(R
.id
.month_list_title
);
426 eventList
= (ListView
) eventListRoot
.findViewById(R
.id
.month_default_list
);
427 eventList
.setSelector(R
.drawable
.no_border
);
428 eventList
.setOnTouchListener(this);
430 } catch (Exception e
) {
431 Log
.e(TAG
, "Error occured creating listview: " + e
.getMessage(), e
);
437 * Flips the current month view either forward or back depending on flip
443 * The number of months to move forward(Positive) or
444 * back(negative). Setting to 0 may cause unexpected behaviour.
447 private void flipMonth(int flipAmount
) {
449 int cur
= monthGridFlipper
.getDisplayedChild();
450 createGridView(false); // We will attach the parent ourselves.
451 AcalDateTime newDate
= (AcalDateTime
) displayedMonth
.clone();
453 // Handle year change
454 newDate
.set(AcalDateTime
.DAY_OF_MONTH
, 1);
455 int newMonth
= newDate
.get(AcalDateTime
.MONTH
) + flipAmount
;
456 int newYear
= newDate
.get(AcalDateTime
.YEAR
);
457 while (newMonth
> AcalDateTime
.DECEMBER
) {
461 while (newMonth
< AcalDateTime
.JANUARY
) {
465 // Set newDate values
466 newDate
.set(AcalDateTime
.YEAR
, newYear
);
467 newDate
.set(AcalDateTime
.MONTH
, newMonth
);
469 // Change this Activities state
470 changeDisplayedMonth(newDate
);
472 // Set the new views parent. We need to ensure that the view does
473 // not already have one.
474 if (monthGrid
.getParent() == monthGridFlipper
)
475 monthGridFlipper
.removeView(monthGrid
);
476 monthGridFlipper
.addView(monthGridRoot
, cur
+ 1);
478 // Make sure View responds to gestures
479 monthGrid
.setFocusableInTouchMode(true);
480 } catch (Exception e
) {
481 Log
.e(TAG
, "Error occured in flipMonth: " + e
.getMessage());
488 * Flips the current Event view either forward or back depending on flip
494 * The number of days to move forward(Positive) or
495 * back(negative). Setting to 0 may cause unexpected behaviour.
498 private void flipDay() {
500 int cur
= listViewFlipper
.getDisplayedChild();
501 createListView(false);
502 listViewFlipper
.addView(eventListRoot
, cur
+ 1);
503 } catch (Exception e
) {
504 Log
.e(TAG
, "Error occured in flipDay: " + e
.getMessage());
510 * Called when user has selected 'Settings' from menu. Starts Settings
514 private void startSettings() {
515 Intent settingsIntent
= new Intent();
516 settingsIntent
.setClassName("com.morphoss.acal",
517 "com.morphoss.acal.activity.Settings");
518 this.startActivity(settingsIntent
);
524 * Called when user has selected 'Tasks' from menu. Starts TodoListView
528 private void startTodoList() {
529 if ( invokedFromView
)
532 Intent todoListIntent
= new Intent();
533 Bundle bundle
= new Bundle();
534 bundle
.putInt("InvokedFromView",1);
535 todoListIntent
.putExtras(bundle
);
536 todoListIntent
.setClassName("com.morphoss.acal",
537 "com.morphoss.acal.activity.TodoListView");
538 this.startActivity(todoListIntent
);
545 * Responsible for nice animation when ViewFlipper changes from one View to
549 * @param objectTouched
551 * The Object that was 'swiped'
555 * Indicates swipe direction. If true, swipe left, else swipe
560 * Swipe success. False if object passed is not 'swipable'
563 private boolean swipe(Object objectTouched
, boolean left
) {
564 if (objectTouched
== null)
566 else if (objectTouched
== monthGrid
) {
568 monthGridFlipper
.setInAnimation(leftIn
);
569 monthGridFlipper
.setOutAnimation(leftOut
);
572 monthGridFlipper
.setInAnimation(rightIn
);
573 monthGridFlipper
.setOutAnimation(rightOut
);
576 int cur
= monthGridFlipper
.getDisplayedChild();
577 monthGridFlipper
.showNext();
578 monthGridFlipper
.removeViewAt(cur
);
581 } else if (objectTouched
== eventList
) {
582 int curMonth
= selectedDate
.get(AcalDateTime
.MONTH
);
583 int dispMonth
= displayedMonth
.get(AcalDateTime
.MONTH
);
584 AcalDateTime newDate
= null;
585 listViewFlipper
.setFlipInterval(0);
587 listViewFlipper
.setInAnimation(leftIn
);
588 listViewFlipper
.setOutAnimation(leftOut
);
589 newDate
= AcalDateTime
.addDays(selectedDate
, 1);
592 listViewFlipper
.setInAnimation(rightIn
);
593 listViewFlipper
.setOutAnimation(rightOut
);
594 newDate
= AcalDateTime
.addDays(selectedDate
, -1);
598 int cur
= listViewFlipper
.getDisplayedChild();
599 listViewFlipper
.showNext();
600 listViewFlipper
.removeViewAt(cur
);
601 changeSelectedDate(newDate
);
602 if (eventList
.getParent() == listViewFlipper
)
603 listViewFlipper
.removeView(eventList
);
604 eventList
.setFocusableInTouchMode(true);
606 // Did the month change?
607 if ((curMonth
== dispMonth
)
608 && (curMonth
!= selectedDate
.get(AcalDateTime
.MONTH
))) {
609 // Flip month as well
610 swipe(monthGrid
, left
);
619 * Determines what object was under the 'finger' of the user when they
625 * The X co-ordinate of the press.
629 * The Y co-ordinate of the press.
632 * The object beneath the press, or null if none.
635 private Object
getTouchedObject(double x
, double y
) {
636 int[] lvc
= new int[2];
637 this.eventList
.getLocationOnScreen(lvc
);
638 int lvh
= this.eventList
.getHeight();
639 int lvw
= this.eventList
.getWidth();
640 if ((x
>= lvc
[0]) && (x
<= lvc
[0] + lvw
) && (y
>= lvc
[1])
641 && (y
<= lvc
[1] + lvh
))
642 return this.eventList
;
644 int[] gvc
= new int[2];
645 this.monthGrid
.getLocationOnScreen(gvc
);
646 int gvh
= this.monthGrid
.getHeight();
647 int gvw
= this.monthGrid
.getWidth();
648 if ((x
>= gvc
[0]) && (x
<= gvc
[0] + gvw
) && (y
>= gvc
[1])
649 && (y
<= gvc
[1] + gvh
))
650 return this.monthGrid
;
655 /****************************************************
657 ****************************************************/
661 * Changes the displayed month to the month represented by the provided
665 public void changeDisplayedMonth(AcalDateTime calendar
) {
666 displayedMonth
= calendar
.applyLocalTimeZone();
667 monthTitle
.setText(AcalDateTime
.fmtMonthYear(calendar
));
668 monthGrid
.setScrollBarStyle(GridView
.SCROLLBARS_INSIDE_OVERLAY
);
669 monthGrid
.setStretchMode(GridView
.STRETCH_COLUMN_WIDTH
);
670 monthGrid
.setPadding(2, 0, 2, 0);
673 ViewParent vp = gridView.getParent();
674 while( vp != null && !(vp instanceof View) ) {
678 int spare = ((View) vp).getWidth() - gridView.getWidth();
679 gridView.setPadding(spare/2, 0, spare/2, 0);
683 if (AcalDateTime
.isWithinMonth(selectedDate
, displayedMonth
))
684 monthGrid
.setAdapter(new MonthAdapter(this, selectedDate
, selectedDate
));
686 monthGrid
.setAdapter(new MonthAdapter(this, displayedMonth
, selectedDate
));
687 monthGrid
.refreshDrawableState();
693 * Changes the selected date to the date represented by the provided
697 public void changeSelectedDate(AcalDateTime c
) {
699 if ( selectedDate
!= null && selectedDate
.equals(c
) ) {
700 rememberCurrentPosition();
703 selectedDate
= c
.applyLocalTimeZone();
707 eventListAdapter
= new EventListAdapter(this, selectedDate
.clone());
709 if ( eventList
== null ) createListView(true);
710 eventList
.setAdapter(eventListAdapter
);
711 eventList
.refreshDrawableState();
712 restoreCurrentPosition();
714 eventListTitle
.setText(AcalDateTime
.fmtDayMonthYear(c
));
716 if (AcalDateTime
.isWithinMonth(selectedDate
, displayedMonth
)) {
717 this.monthGrid
.setAdapter(new MonthAdapter(this, displayedMonth
.clone(), selectedDate
.clone()));
718 this.monthGrid
.refreshDrawableState();
720 MonthAdapter ma
= ((MonthAdapter
) this.monthGrid
.getAdapter());
722 ma
= new MonthAdapter(this, displayedMonth
.clone(), selectedDate
.clone());
723 ma
.updateSelectedDay(selectedDate
);
724 this.monthGrid
.refreshDrawableState();
730 * Methods for managing event structure
732 public ArrayList
<SimpleAcalEvent
> getEventsForDay(AcalDateTime day
) {
733 if (dataRequest
== null) {
734 Log
.w(TAG
,"DataService connection not available!");
735 return new ArrayList
<SimpleAcalEvent
>();
738 return (ArrayList
<SimpleAcalEvent
>) dataRequest
.getEventsForDay(day
);
740 catch (RemoteException e
) {
741 if (Constants
.LOG_DEBUG
) Log
.d(TAG
,"Remote Exception accessing eventcache: "+e
);
742 return new ArrayList
<SimpleAcalEvent
>();
746 public int getNumberEventsForDay(AcalDateTime day
) {
747 if (dataRequest
== null) return 0;
749 return dataRequest
.getNumberEventsForDay(day
);
750 } catch (RemoteException e
) {
751 if (Constants
.LOG_DEBUG
) Log
.d(TAG
,"Remote Exception accessing eventcache: "+e
);
756 public SimpleAcalEvent
getNthEventForDay(AcalDateTime day
, int n
) {
757 if (dataRequest
== null) return null;
759 return dataRequest
.getNthEventForDay(day
, n
);
760 } catch (RemoteException e
) {
761 if (Constants
.LOG_DEBUG
) Log
.d(TAG
,"Remote Exception accessing eventcache: "+e
);
766 public void deleteEvent(AcalDateTime day
, int n
, int action
) {
767 if (dataRequest
== null) return;
769 SimpleAcalEvent sae
= dataRequest
.getNthEventForDay(day
, n
);
770 AcalEvent ae
= AcalEvent
.fromDatabase(this, sae
.resourceId
, new AcalDateTime().setEpoch(sae
.start
));
771 ae
.setAction(action
);
772 dataRequest
.eventChanged(ae
);
773 } catch (RemoteException e
) {
774 Log
.e(TAG
,"RemoteException while deleting event: ",e
);
775 } catch (Exception e
) {
776 Log
.e(TAG
,"Exception while deleting event: ",e
);
778 this.changeSelectedDate(this.selectedDate
);
782 /********************************************************************
783 * Implemented Interface Overrides *
784 ********************************************************************/
788 * Responsible for handling the menu button push.
791 * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
794 public boolean onCreateOptionsMenu(Menu menu
) {
795 MenuInflater inflater
= getMenuInflater();
796 inflater
.inflate(R
.menu
.events_options_menu
, menu
);
802 * Called when user has touched the screen. Handled by our Gesture Detector.
807 * @see android.app.Activity#onTouchEvent(android.view.MotionEvent)
810 public boolean onTouchEvent(MotionEvent event
) {
811 if (Constants
.debugMonthView
&& Constants
.LOG_VERBOSE
) Log
.v(TAG
, "onTouchEvent called at (" + event
.getRawX() + ","
812 + event
.getRawY() + ")");
813 return gestureDetector
.onTouchEvent(event
);
818 * Called when user has touched the screen. Handled by our Gesture Detector.
823 * @see android.view.View.OnTouchListener#onTouch(android.view.View,
824 * android.view.MotionEvent)
827 public boolean onTouch(View view
, MotionEvent touch
) {
828 if (Constants
.debugMonthView
&& Constants
.LOG_VERBOSE
) Log
.v(TAG
, "onTouch called with touch at (" + touch
.getRawX() + ","
829 + touch
.getRawY() + ") touching view " + view
.getId());
830 return this.gestureDetector
.onTouchEvent(touch
);
835 * Called when the user selects an option from the options menu. Determines
836 * what (if any) Activity should start.
839 * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
842 public boolean onOptionsItemSelected(MenuItem item
) {
843 // Handle item selection
844 switch (item
.getItemId()) {
845 case R
.id
.settingsMenuItem
:
848 case R
.id
.tasksMenuItem
:
852 return super.onOptionsItemSelected(item
);
858 * The main handler for Gestures in this activity. At this time we are only
859 * interested in scroll gestures. Determines what object the gesture
860 * occurred on, whether the gesture is suitable to respond to, and finally,
861 * kicks of an appropriate response.
865 public boolean onScroll(MotionEvent start
, MotionEvent current
, float dx
,
868 // We can't work with null objects
869 if (start
== null || current
== null)
872 // Calculate all values required to identify if we need to react
873 double startX
= start
.getRawX();
874 double startY
= start
.getRawY();
875 double distX
= current
.getRawX() - startX
;
876 double distY
= current
.getRawY() - startY
;
877 double totalDist
= Math
.sqrt(Math
.pow(distX
, 2)
878 + Math
.pow(distY
, 2));
879 double angle
= 180 + ((Math
.atan2(distY
, distX
)) * (180.0 / Math
.PI
));
880 Object scrolledOn
= getTouchedObject(startX
, startY
);
881 boolean isHorizontal
= false;
882 boolean isVertical
= false;
883 if ((angle
> 360 - maxAngleDev
|| angle
< 0 + maxAngleDev
)
884 || (angle
> 180 - maxAngleDev
&& angle
< 180 + maxAngleDev
)) {
886 } else if ((angle
> 90 - maxAngleDev
&& angle
< 90 + maxAngleDev
)
887 || (angle
> 270 - maxAngleDev
&& angle
< 270 + maxAngleDev
)) {
891 // Report calculations
892 if (Constants
.debugMonthView
&& Constants
.LOG_DEBUG
) Log
.d(TAG
, "onScroll called with onDown at (" + startX
+ ","
893 + startY
+ ") " + "and with distance (" + distX
+ ","
894 + distY
+ "), " + "angle is" + angle
);
896 // Some conditions that work out if we are interested in this event.
897 if ((consumedX
== startX
&& consumedY
== startY
) || // We've already
900 (totalDist
< minDistance
) || // Not a long enough swipe
901 (scrolledOn
== null) || // Nothing underneath touch of
903 (!isHorizontal
&& !isVertical
) // Direction is not of
906 if (Constants
.debugMonthView
&& Constants
.LOG_DEBUG
)Log
.d(TAG
, "onScroll ignored.");
910 long startFlip
= System
.currentTimeMillis();
911 if (Constants
.debugMonthView
&& Constants
.LOG_DEBUG
)Log
.d(TAG
, "Valid onScroll detected.");
913 // If we are here, we have a valid scroll event to process
914 if (monthGrid
!= null && scrolledOn
== monthGrid
) {
917 swipe(monthGrid
, false);
919 swipe(monthGrid
, true);
923 } else if (isVertical
) {
926 } else if (eventList
!= null && scrolledOn
== eventList
) {
928 this.eventListAdapter
.setClickEnabled(false);
930 swipe(eventList
, false);
932 swipe(eventList
, true);
935 if (Constants
.debugMonthView
&& Constants
.LOG_DEBUG
) Log
.d(TAG
,
937 + (System
.currentTimeMillis() - startFlip
)
942 } catch (Exception e
) {
944 "Unknown error occurred processing scroll event: "
945 + e
.getMessage() + Log
.getStackTraceString(e
));
951 public boolean onTrackballEvent(MotionEvent motion
) {
952 switch (motion
.getAction()) {
953 case (MotionEvent
.ACTION_MOVE
): {
954 int dx
= (int) (motion
.getX() * motion
.getXPrecision() );
955 int dy
= (int) (motion
.getY() * motion
.getYPrecision() );
956 // if ( Constants.LOG_VERBOSE )
957 // Log.v(TAG,"Trackball event of size "+motion.getHistorySize()+" x/y"+motion.getX()+"/"+motion.getY()
958 // + " - precision: " + motion.getXPrecision() +"/" + motion.getYPrecision());
960 swipe(eventList
, true);
962 swipe(eventList
, false);
964 changeSelectedDate(selectedDate
.addDays(7));
966 changeSelectedDate(selectedDate
.addDays(-7));
970 case (MotionEvent
.ACTION_DOWN
): {
971 // logic for ACTION_DOWN motion event here
974 case (MotionEvent
.ACTION_UP
): {
975 // logic for ACTION_UP motion event here
984 * Handles button Clicks
988 public void onClick(View clickedView
) {
989 int button
= (int) ((Integer
) clickedView
.getTag());
992 AcalDateTime cal
= new AcalDateTime();
994 if (cal
.getEpochDay() == this.selectedDate
.getEpochDay()) {
995 this.monthGridFlipper
.setAnimation(AnimationUtils
.loadAnimation(this, R
.anim
.shake
));
996 this.listViewFlipper
.setAnimation(AnimationUtils
.loadAnimation(this, R
.anim
.shake
));
998 this.changeSelectedDate(cal
);
999 this.changeDisplayedMonth(cal
);
1003 Bundle bundle
= new Bundle();
1004 bundle
.putParcelable("DATE", this.selectedDate
);
1005 Intent eventEditIntent
= new Intent(this, EventEdit
.class);
1006 eventEditIntent
.putExtras(bundle
);
1007 this.startActivity(eventEditIntent
);
1010 if (prefs
.getBoolean(getString(R
.string
.prefDefaultView
), false)) {
1014 bundle
= new Bundle();
1015 bundle
.putParcelable("StartDay", selectedDate
);
1016 Intent weekIntent
= new Intent(this, WeekViewActivity
.class);
1017 weekIntent
.putExtras(bundle
);
1018 this.startActivityForResult(weekIntent
, PICK_DAY_FROM_WEEK_VIEW
);
1022 bundle
= new Bundle();
1023 bundle
.putInt("StartYear", selectedDate
.getYear());
1024 Intent yearIntent
= new Intent(this, YearView
.class);
1025 yearIntent
.putExtras(bundle
);
1026 this.startActivityForResult(yearIntent
, PICK_MONTH_FROM_YEAR_VIEW
);
1029 Log
.w(TAG
, "Unrecognised button was pushed in MonthView.");
1033 /************************************************************************
1034 * Service Connection management *
1035 ************************************************************************/
1037 private ServiceConnection mConnection
= new ServiceConnection() {
1038 public void onServiceConnected(ComponentName className
, IBinder service
) {
1039 // This is called when the connection with the service has been
1040 // established, giving us the service object we can use to
1041 // interact with the service. We are communicating with our
1042 // service through an IDL interface, so get a client-side
1043 // representation of that from the raw service object.
1044 dataRequest
= DataRequest
.Stub
.asInterface(service
);
1046 dataRequest
.registerCallback(mCallback
);
1048 } catch (RemoteException re
) {
1049 Log
.d(TAG
,Log
.getStackTraceString(re
));
1051 serviceIsConnected();
1054 public void onServiceDisconnected(ComponentName className
) {
1055 serviceIsDisconnected();
1060 * This implementation is used to receive callbacks from the remote service.
1062 private DataRequestCallBack mCallback
= new DataRequestCallBack
.Stub() {
1064 * This is called by the remote service regularly to tell us about new
1065 * values. Note that IPC calls are dispatched through a thread pool
1066 * running in each process, so the code executing here will NOT be
1067 * running in our main thread like most other things -- so, to update
1068 * the UI, we need to use a Handler to hop over there.
1070 public void statusChanged(int type
, boolean value
) {
1071 mHandler
.sendMessage(mHandler
.obtainMessage(BUMP_MSG
, type
,
1076 private static final int BUMP_MSG
= 1;
1078 private Handler mHandler
= new Handler() {
1080 public void handleMessage(Message msg
) {
1081 int type
= msg
.arg1
;
1083 case CalendarDataService
.UPDATE
:
1084 if ( Constants
.LOG_DEBUG
)
1085 Log
.i(TAG
, "Received update notification from CalendarDataService.");
1086 changeSelectedDate(selectedDate
);
1095 public boolean onContextItemSelected(MenuItem item
) {
1096 return this.eventListAdapter
.contextClick(item
);
1100 protected void onActivityResult(int requestCode
, int resultCode
, Intent data
) {
1101 if ( resultCode
== RESULT_OK
) {
1102 switch ( requestCode
) {
1103 case PICK_DAY_FROM_WEEK_VIEW
:
1104 if (data
.hasExtra("selectedDate")) {
1106 AcalDateTime day
= (AcalDateTime
) data
.getParcelableExtra("selectedDate");
1107 this.changeSelectedDate(day
);
1108 this.changeDisplayedMonth(day
);
1109 } catch (Exception e
) {
1110 Log
.w(TAG
, "Error getting month back from year view: "+e
);
1114 case PICK_MONTH_FROM_YEAR_VIEW
:
1115 if (data
.hasExtra("selectedDate")) {
1117 AcalDateTime month
= (AcalDateTime
) data
.getParcelableExtra("selectedDate");
1118 this.changeDisplayedMonth(month
);
1119 } catch (Exception e
) {
1120 Log
.w(TAG
, "Error getting month back from year view: "+e
);
1124 case PICK_TODAY_FROM_EVENT_VIEW
:
1126 AcalDateTime chosenDate
= (AcalDateTime
) data
.getParcelableExtra("selectedDate");
1127 this.changeDisplayedMonth(chosenDate
);
1128 this.changeSelectedDate(chosenDate
);
1129 } catch (Exception e
) {
1130 Log
.w(TAG
, "Error getting month back from year view: "+e
);
1134 if (Constants
.LOG_DEBUG
) Log
.d(TAG
, "Writing month view state to file.");
1135 ObjectOutputStream outputStream
= null;
1137 outputStream
= new ObjectOutputStream(new FileOutputStream(
1139 outputStream
.writeObject(this.selectedDate
);
1140 outputStream
.writeObject(this.displayedMonth
);
1141 } catch (FileNotFoundException ex
) {
1143 "Error saving MonthView State - File Not Found: "
1145 } catch (IOException ex
) {
1147 "Error saving MonthView State - IO Error: "
1150 // Close the ObjectOutputStream
1152 if (outputStream
!= null) {
1153 outputStream
.flush();
1154 outputStream
.close();
1156 } catch (IOException ex
) {
1157 Log
.w(TAG
, "Error closing MonthView file - IO Error: "
1164 /************************************************************************
1165 * Required Overrides that aren't used *
1166 ************************************************************************/
1169 public boolean onDown(MotionEvent downEvent
) {
1174 public boolean onFling(MotionEvent arg0
, MotionEvent arg1
, float arg2
,
1180 public void onLongPress(MotionEvent downEvent
) {
1184 public void onShowPress(MotionEvent downEvent
) {
1188 public boolean onSingleTapUp(MotionEvent upEvent
) {