Hack around inexplicable force close from market report.
[acal.git] / src / com / morphoss / acal / activity / MonthView.java
blobaf03a265011139b4a7e4cc2d23d776a3b261ce83
1 /*
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;
67 /**
68 * <h1>Month View</h1>
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>
73 * <p>
74 * This view is split into 3 sections:
75 * </p>
76 * <ul>
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>
82 * </ul>
83 * <p>
84 * As well as this, there is a menu accessible through the menu button.
85 * </p>
87 * <p>
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
91 * representing.
92 * </p>
94 * <p>
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.
101 * </p>
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;
170 // Animations
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 ********************************************************/
184 * <p>
185 * Called when Activity is first created. Initialises all appropriate fields
186 * and Constructs the Views for display.
187 * </p>
189 * (non-Javadoc)
191 * @see android.app.Activity#onCreate(android.os.Bundle)
193 @Override
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);
207 // Set up buttons
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() {
225 try {
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);
231 intent.putExtras(b);
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;
251 * <p>
252 * Called when Activity regains focus. Try's to load the saved State.
253 * </p>
255 * (non-Javadoc)
257 * @see android.app.Activity#onResume()
259 @Override
260 public void onResume() {
261 super.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);
273 connectToService();
277 * <p>
278 * Called when activity loses focus or is closed. Try's to save the current
279 * State
280 * </p>
282 * (non-Javadoc)
284 * @see android.app.Activity#onPause()
286 @Override
287 public void onPause() {
288 super.onPause();
290 rememberCurrentPosition();
291 eventList = null;
292 try {
293 if (dataRequest != null) {
294 dataRequest.flushCache();
295 dataRequest.unregisterCallback(mCallback);
297 this.unbindService(mConnection);
299 catch (RemoteException re) { }
300 catch (IllegalArgumentException e) { }
301 finally {
302 dataRequest = null;
305 // Save state
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 /****************************************************
333 * Private Methods *
334 ****************************************************/
337 * <p>
338 * Helper method for setting up buttons
339 * </p>
340 * @param buttonLabel
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 + "'");
347 else {
348 myButton.setText(buttonLabel);
349 myButton.setOnClickListener(this);
350 myButton.setTag(val);
351 AcalTheme.setContainerFromTheme(myButton, AcalTheme.BUTTON);
356 * <p>
357 * Creates a new GridView object based on this Activities current state. The
358 * GridView created Will display this Activities MonthView
359 * </p>
361 * @param addParent
362 * <p>
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.
366 * </p>
368 private void createGridView(boolean addParent) {
370 try {
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));
381 // Title
382 monthTitle = (TextView) monthGridRoot
383 .findViewById(R.id.month_grid_title);
384 // Grid
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());
395 * <p>
396 * Creates a new GridView object based on this Activities current state. The
397 * GridView created will display this Activities ListView
398 * </p>
400 * @param addParent
401 * <p>
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.
405 * </p>
407 private void createListView(boolean addParent) {
408 try {
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
416 if (addParent)
417 eventListRoot = inflater.inflate(R.layout.month_list_view,
418 listViewFlipper);
419 else
420 eventListRoot = inflater.inflate(R.layout.month_list_view, null);
422 // Title
423 eventListTitle = (TextView) eventListRoot.findViewById(R.id.month_list_title);
425 // List
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);
436 * <p>
437 * Flips the current month view either forward or back depending on flip
438 * amount parameter.
439 * </p>
441 * @param flipAmount
442 * <p>
443 * The number of months to move forward(Positive) or
444 * back(negative). Setting to 0 may cause unexpected behaviour.
445 * </p>
447 private void flipMonth(int flipAmount) {
448 try {
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) {
458 newMonth -= 12;
459 newYear++;
461 while (newMonth < AcalDateTime.JANUARY) {
462 newMonth += 12;
463 newYear--;
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());
487 * <p>
488 * Flips the current Event view either forward or back depending on flip
489 * amount parameter.
490 * </p>
492 * @param flipAmount
493 * <p>
494 * The number of days to move forward(Positive) or
495 * back(negative). Setting to 0 may cause unexpected behaviour.
496 * </p>
498 private void flipDay() {
499 try {
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());
509 * <p>
510 * Called when user has selected 'Settings' from menu. Starts Settings
511 * Activity.
512 * </p>
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);
523 * <p>
524 * Called when user has selected 'Tasks' from menu. Starts TodoListView
525 * Activity.
526 * </p>
528 private void startTodoList() {
529 if ( invokedFromView )
530 this.finish();
531 else {
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);
544 * <p>
545 * Responsible for nice animation when ViewFlipper changes from one View to
546 * the Next.
547 * </p>
549 * @param objectTouched
550 * <p>
551 * The Object that was 'swiped'
552 * </p>
553 * @param left
554 * <p>
555 * Indicates swipe direction. If true, swipe left, else swipe
556 * right.
557 * </p>
559 * @return <p>
560 * Swipe success. False if object passed is not 'swipable'
561 * </p>
563 private boolean swipe(Object objectTouched, boolean left) {
564 if (objectTouched == null)
565 return false;
566 else if (objectTouched == monthGrid) {
567 if (left) {
568 monthGridFlipper.setInAnimation(leftIn);
569 monthGridFlipper.setOutAnimation(leftOut);
570 flipMonth(1);
571 } else {
572 monthGridFlipper.setInAnimation(rightIn);
573 monthGridFlipper.setOutAnimation(rightOut);
574 flipMonth(-1);
576 int cur = monthGridFlipper.getDisplayedChild();
577 monthGridFlipper.showNext();
578 monthGridFlipper.removeViewAt(cur);
579 return true;
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);
586 if (left) {
587 listViewFlipper.setInAnimation(leftIn);
588 listViewFlipper.setOutAnimation(leftOut);
589 newDate = AcalDateTime.addDays(selectedDate, 1);
590 flipDay();
591 } else {
592 listViewFlipper.setInAnimation(rightIn);
593 listViewFlipper.setOutAnimation(rightOut);
594 newDate = AcalDateTime.addDays(selectedDate, -1);
595 flipDay();
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);
612 return true;
614 return false;
618 * <p>
619 * Determines what object was under the 'finger' of the user when they
620 * started a gesture.
621 * </p>
623 * @param x
624 * <p>
625 * The X co-ordinate of the press.
626 * </p>
627 * @param y
628 * <p>
629 * The Y co-ordinate of the press.
630 * </p>
631 * @return <p>
632 * The object beneath the press, or null if none.
633 * </p>
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;
652 return null;
655 /****************************************************
656 * Public Methods *
657 ****************************************************/
660 * <p>
661 * Changes the displayed month to the month represented by the provided
662 * calendar.
663 * </p>
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) ) {
675 vp = vp.getParent();
677 if ( vp != null ) {
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));
685 else
686 monthGrid.setAdapter(new MonthAdapter(this, displayedMonth, selectedDate));
687 monthGrid.refreshDrawableState();
692 * <p>
693 * Changes the selected date to the date represented by the provided
694 * calendar.
695 * </p>
697 public void changeSelectedDate(AcalDateTime c) {
699 if ( selectedDate != null && selectedDate.equals(c) ) {
700 rememberCurrentPosition();
702 else {
703 selectedDate = c.applyLocalTimeZone();
704 eventListTop = 0;
705 eventListIndex = 0;
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();
719 } else {
720 MonthAdapter ma = ((MonthAdapter) this.monthGrid.getAdapter());
721 if ( ma == null )
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>();
737 try {
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;
748 try {
749 return dataRequest.getNumberEventsForDay(day);
750 } catch (RemoteException e) {
751 if (Constants.LOG_DEBUG) Log.d(TAG,"Remote Exception accessing eventcache: "+e);
752 return 0;
756 public SimpleAcalEvent getNthEventForDay(AcalDateTime day, int n) {
757 if (dataRequest == null) return null;
758 try {
759 return dataRequest.getNthEventForDay(day, n);
760 } catch (RemoteException e) {
761 if (Constants.LOG_DEBUG) Log.d(TAG,"Remote Exception accessing eventcache: "+e);
762 return null;
766 public void deleteEvent(AcalDateTime day, int n, int action ) {
767 if (dataRequest == null) return;
768 try {
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 ********************************************************************/
787 * <p>
788 * Responsible for handling the menu button push.
789 * </p>
791 * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
793 @Override
794 public boolean onCreateOptionsMenu(Menu menu) {
795 MenuInflater inflater = getMenuInflater();
796 inflater.inflate(R.menu.events_options_menu, menu);
797 return true;
801 * <p>
802 * Called when user has touched the screen. Handled by our Gesture Detector.
803 * </p>
805 * (non-Javadoc)
807 * @see android.app.Activity#onTouchEvent(android.view.MotionEvent)
809 @Override
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);
817 * <p>
818 * Called when user has touched the screen. Handled by our Gesture Detector.
819 * </p>
821 * (non-Javadoc)
823 * @see android.view.View.OnTouchListener#onTouch(android.view.View,
824 * android.view.MotionEvent)
826 @Override
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);
834 * <p>
835 * Called when the user selects an option from the options menu. Determines
836 * what (if any) Activity should start.
837 * </p>
839 * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
841 @Override
842 public boolean onOptionsItemSelected(MenuItem item) {
843 // Handle item selection
844 switch (item.getItemId()) {
845 case R.id.settingsMenuItem:
846 startSettings();
847 return true;
848 case R.id.tasksMenuItem:
849 startTodoList();
850 return true;
851 default:
852 return super.onOptionsItemSelected(item);
857 * <p>
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.
862 * </p>
864 @Override
865 public boolean onScroll(MotionEvent start, MotionEvent current, float dx,
866 float dy) {
867 try {
868 // We can't work with null objects
869 if (start == null || current == null)
870 return false;
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)) {
885 isHorizontal = true;
886 } else if ((angle > 90 - maxAngleDev && angle < 90 + maxAngleDev)
887 || (angle > 270 - maxAngleDev && angle < 270 + maxAngleDev)) {
888 isVertical = true;
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
898 // consumed the
899 // event
900 (totalDist < minDistance) || // Not a long enough swipe
901 (scrolledOn == null) || // Nothing underneath touch of
902 // interest
903 (!isHorizontal && !isVertical) // Direction is not of
904 // intrest
906 if (Constants.debugMonthView && Constants.LOG_DEBUG)Log.d(TAG, "onScroll ignored.");
907 return false;
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) {
915 if (isHorizontal) {
916 if (distX > 0)
917 swipe(monthGrid, false);
918 else
919 swipe(monthGrid, true);
920 consumedX = startX;
921 consumedY = startY;
922 return true;
923 } else if (isVertical) {
924 return false;
926 } else if (eventList != null && scrolledOn == eventList) {
927 if (isHorizontal) {
928 this.eventListAdapter.setClickEnabled(false);
929 if (distX > 0)
930 swipe(eventList, false);
931 else
932 swipe(eventList, true);
933 consumedX = startX;
934 consumedY = startY;
935 if (Constants.debugMonthView && Constants.LOG_DEBUG) Log.d(TAG,
936 "Scroll took ."
937 + (System.currentTimeMillis() - startFlip)
938 + "ms to process.");
939 return true;
942 } catch (Exception e) {
943 Log.e(TAG,
944 "Unknown error occurred processing scroll event: "
945 + e.getMessage() + Log.getStackTraceString(e));
947 return false;
950 @Override
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());
959 if ( dx > 0 )
960 swipe(eventList, true);
961 else if ( dx < 0 )
962 swipe(eventList, false);
963 else if ( dy > 0 )
964 changeSelectedDate(selectedDate.addDays(7));
965 else if ( dy < 0 )
966 changeSelectedDate(selectedDate.addDays(-7));
968 break;
970 case (MotionEvent.ACTION_DOWN): {
971 // logic for ACTION_DOWN motion event here
972 break;
974 case (MotionEvent.ACTION_UP): {
975 // logic for ACTION_UP motion event here
976 break;
979 return true;
983 * <p>
984 * Handles button Clicks
985 * </p>
987 @Override
988 public void onClick(View clickedView) {
989 int button = (int) ((Integer) clickedView.getTag());
990 switch (button) {
991 case TODAY:
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);
1001 break;
1002 case ADD:
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);
1008 break;
1009 case WEEK:
1010 if (prefs.getBoolean(getString(R.string.prefDefaultView), false)) {
1011 this.finish();
1013 else {
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);
1020 break;
1021 case YEAR:
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);
1027 break;
1028 default:
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);
1045 try {
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,
1072 (value ? 1 : 0)));
1076 private static final int BUMP_MSG = 1;
1078 private Handler mHandler = new Handler() {
1079 @Override
1080 public void handleMessage(Message msg) {
1081 int type = msg.arg1;
1082 switch ( type ) {
1083 case CalendarDataService.UPDATE:
1084 if ( Constants.LOG_DEBUG )
1085 Log.i(TAG, "Received update notification from CalendarDataService.");
1086 changeSelectedDate(selectedDate);
1087 break;
1094 @Override
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")) {
1105 try {
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);
1113 break;
1114 case PICK_MONTH_FROM_YEAR_VIEW:
1115 if (data.hasExtra("selectedDate")) {
1116 try {
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);
1123 break;
1124 case PICK_TODAY_FROM_EVENT_VIEW:
1125 try {
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);
1133 // Save state
1134 if (Constants.LOG_DEBUG) Log.d(TAG, "Writing month view state to file.");
1135 ObjectOutputStream outputStream = null;
1136 try {
1137 outputStream = new ObjectOutputStream(new FileOutputStream(
1138 STATE_FILE));
1139 outputStream.writeObject(this.selectedDate);
1140 outputStream.writeObject(this.displayedMonth);
1141 } catch (FileNotFoundException ex) {
1142 Log.w(TAG,
1143 "Error saving MonthView State - File Not Found: "
1144 + ex.getMessage());
1145 } catch (IOException ex) {
1146 Log.w(TAG,
1147 "Error saving MonthView State - IO Error: "
1148 + ex.getMessage());
1149 } finally {
1150 // Close the ObjectOutputStream
1151 try {
1152 if (outputStream != null) {
1153 outputStream.flush();
1154 outputStream.close();
1156 } catch (IOException ex) {
1157 Log.w(TAG, "Error closing MonthView file - IO Error: "
1158 + ex.getMessage());
1164 /************************************************************************
1165 * Required Overrides that aren't used *
1166 ************************************************************************/
1168 @Override
1169 public boolean onDown(MotionEvent downEvent) {
1170 return false;
1173 @Override
1174 public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2,
1175 float arg3) {
1176 return false;
1179 @Override
1180 public void onLongPress(MotionEvent downEvent) {
1183 @Override
1184 public void onShowPress(MotionEvent downEvent) {
1187 @Override
1188 public boolean onSingleTapUp(MotionEvent upEvent) {
1189 return false;