blob: b95c7c3d6c7162a7d903010530711e788f7a949a [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.calendar;
import static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY;
import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
import static com.android.calendar.CalendarController.EVENT_EDIT_ON_LAUNCH;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.app.Service;
import android.content.ActivityNotFoundException;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.CalendarContract;
import android.provider.CalendarContract.Attendees;
import android.provider.CalendarContract.Calendars;
import android.provider.CalendarContract.Events;
import android.provider.CalendarContract.Reminders;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.QuickContact;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.format.Time;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.text.style.ForegroundColorSpan;
import android.text.util.Rfc822Token;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.calendar.CalendarController.EventInfo;
import com.android.calendar.CalendarController.EventType;
import com.android.calendar.alerts.QuickResponseActivity;
import com.android.calendarcommon2.DateException;
import com.android.calendarcommon2.Duration;
import com.android.calendarcommon2.EventRecurrence;
import com.android.colorpicker.HsvColorComparator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class EventInfoFragment extends DialogFragment implements OnCheckedChangeListener,
CalendarController.EventHandler, OnClickListener {
public static final boolean DEBUG = false;
public static final String TAG = "EventInfoFragment";
protected static final String BUNDLE_KEY_EVENT_ID = "key_event_id";
protected static final String BUNDLE_KEY_START_MILLIS = "key_start_millis";
protected static final String BUNDLE_KEY_END_MILLIS = "key_end_millis";
protected static final String BUNDLE_KEY_IS_DIALOG = "key_fragment_is_dialog";
protected static final String BUNDLE_KEY_DELETE_DIALOG_VISIBLE = "key_delete_dialog_visible";
protected static final String BUNDLE_KEY_WINDOW_STYLE = "key_window_style";
protected static final String BUNDLE_KEY_CALENDAR_COLOR = "key_calendar_color";
protected static final String BUNDLE_KEY_CALENDAR_COLOR_INIT = "key_calendar_color_init";
protected static final String BUNDLE_KEY_CURRENT_COLOR = "key_current_color";
protected static final String BUNDLE_KEY_CURRENT_COLOR_KEY = "key_current_color_key";
protected static final String BUNDLE_KEY_CURRENT_COLOR_INIT = "key_current_color_init";
protected static final String BUNDLE_KEY_ORIGINAL_COLOR = "key_original_color";
protected static final String BUNDLE_KEY_ORIGINAL_COLOR_INIT = "key_original_color_init";
protected static final String BUNDLE_KEY_ATTENDEE_RESPONSE = "key_attendee_response";
protected static final String BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE =
"key_user_set_attendee_response";
protected static final String BUNDLE_KEY_TENTATIVE_USER_RESPONSE =
"key_tentative_user_response";
protected static final String BUNDLE_KEY_RESPONSE_WHICH_EVENTS = "key_response_which_events";
protected static final String BUNDLE_KEY_REMINDER_MINUTES = "key_reminder_minutes";
protected static final String BUNDLE_KEY_REMINDER_METHODS = "key_reminder_methods";
private static final String PERIOD_SPACE = ". ";
private static final String NO_EVENT_COLOR = "";
/**
* These are the corresponding indices into the array of strings
* "R.array.change_response_labels" in the resource file.
*/
static final int UPDATE_SINGLE = 0;
static final int UPDATE_ALL = 1;
// Style of view
public static final int FULL_WINDOW_STYLE = 0;
public static final int DIALOG_WINDOW_STYLE = 1;
private int mWindowStyle = DIALOG_WINDOW_STYLE;
// Query tokens for QueryHandler
private static final int TOKEN_QUERY_EVENT = 1 << 0;
private static final int TOKEN_QUERY_CALENDARS = 1 << 1;
private static final int TOKEN_QUERY_ATTENDEES = 1 << 2;
private static final int TOKEN_QUERY_DUPLICATE_CALENDARS = 1 << 3;
private static final int TOKEN_QUERY_REMINDERS = 1 << 4;
private static final int TOKEN_QUERY_VISIBLE_CALENDARS = 1 << 5;
private static final int TOKEN_QUERY_COLORS = 1 << 6;
private static final int TOKEN_QUERY_ALL = TOKEN_QUERY_DUPLICATE_CALENDARS
| TOKEN_QUERY_ATTENDEES | TOKEN_QUERY_CALENDARS | TOKEN_QUERY_EVENT
| TOKEN_QUERY_REMINDERS | TOKEN_QUERY_VISIBLE_CALENDARS | TOKEN_QUERY_COLORS;
private int mCurrentQuery = 0;
private static final String[] EVENT_PROJECTION = new String[] {
Events._ID, // 0 do not remove; used in DeleteEventHelper
Events.TITLE, // 1 do not remove; used in DeleteEventHelper
Events.RRULE, // 2 do not remove; used in DeleteEventHelper
Events.ALL_DAY, // 3 do not remove; used in DeleteEventHelper
Events.CALENDAR_ID, // 4 do not remove; used in DeleteEventHelper
Events.DTSTART, // 5 do not remove; used in DeleteEventHelper
Events._SYNC_ID, // 6 do not remove; used in DeleteEventHelper
Events.EVENT_TIMEZONE, // 7 do not remove; used in DeleteEventHelper
Events.DESCRIPTION, // 8
Events.EVENT_LOCATION, // 9
Calendars.CALENDAR_ACCESS_LEVEL, // 10
Events.CALENDAR_COLOR, // 11
Events.EVENT_COLOR, // 12
Events.HAS_ATTENDEE_DATA, // 13
Events.ORGANIZER, // 14
Events.HAS_ALARM, // 15
Calendars.MAX_REMINDERS, // 16
Calendars.ALLOWED_REMINDERS, // 17
Events.CUSTOM_APP_PACKAGE, // 18
Events.CUSTOM_APP_URI, // 19
Events.DTEND, // 20
Events.DURATION, // 21
Events.ORIGINAL_SYNC_ID // 22 do not remove; used in DeleteEventHelper
};
private static final int EVENT_INDEX_ID = 0;
private static final int EVENT_INDEX_TITLE = 1;
private static final int EVENT_INDEX_RRULE = 2;
private static final int EVENT_INDEX_ALL_DAY = 3;
private static final int EVENT_INDEX_CALENDAR_ID = 4;
private static final int EVENT_INDEX_DTSTART = 5;
private static final int EVENT_INDEX_SYNC_ID = 6;
private static final int EVENT_INDEX_EVENT_TIMEZONE = 7;
private static final int EVENT_INDEX_DESCRIPTION = 8;
private static final int EVENT_INDEX_EVENT_LOCATION = 9;
private static final int EVENT_INDEX_ACCESS_LEVEL = 10;
private static final int EVENT_INDEX_CALENDAR_COLOR = 11;
private static final int EVENT_INDEX_EVENT_COLOR = 12;
private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 13;
private static final int EVENT_INDEX_ORGANIZER = 14;
private static final int EVENT_INDEX_HAS_ALARM = 15;
private static final int EVENT_INDEX_MAX_REMINDERS = 16;
private static final int EVENT_INDEX_ALLOWED_REMINDERS = 17;
private static final int EVENT_INDEX_CUSTOM_APP_PACKAGE = 18;
private static final int EVENT_INDEX_CUSTOM_APP_URI = 19;
private static final int EVENT_INDEX_DTEND = 20;
private static final int EVENT_INDEX_DURATION = 21;
static {
if (!Utils.isJellybeanOrLater()) {
EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_PACKAGE] = Events._ID; // dummy value
EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_URI] = Events._ID; // dummy value
}
}
static final String[] CALENDARS_PROJECTION = new String[] {
Calendars._ID, // 0
Calendars.CALENDAR_DISPLAY_NAME, // 1
Calendars.OWNER_ACCOUNT, // 2
Calendars.CAN_ORGANIZER_RESPOND, // 3
Calendars.ACCOUNT_NAME, // 4
Calendars.ACCOUNT_TYPE // 5
};
static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3;
static final int CALENDARS_INDEX_ACCOUNT_NAME = 4;
static final int CALENDARS_INDEX_ACCOUNT_TYPE = 5;
static final String CALENDARS_WHERE = Calendars._ID + "=?";
static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.CALENDAR_DISPLAY_NAME + "=?";
static final String CALENDARS_VISIBLE_WHERE = Calendars.VISIBLE + "=?";
public static final int COLORS_INDEX_COLOR = 1;
public static final int COLORS_INDEX_COLOR_KEY = 2;
private View mView;
private Uri mUri;
private long mEventId;
private Cursor mEventCursor;
private Cursor mCalendarsCursor;
private static float mScale = 0; // Used for supporting different screen densities
private static int mCustomAppIconSize = 32;
private long mStartMillis;
private long mEndMillis;
private boolean mAllDay;
private boolean mOwnerCanRespond;
private String mSyncAccountName;
private String mCalendarOwnerAccount;
private boolean mIsBusyFreeCalendar;
private int mOriginalAttendeeResponse;
private int mAttendeeResponseFromIntent = Attendees.ATTENDEE_STATUS_NONE;
private int mUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
private int mWhichEvents = -1;
// Used as the temporary response until the dialog is confirmed. It is also
// able to be used as a state marker for configuration changes.
private int mTentativeUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
private boolean mHasAlarm;
// Used to prevent saving changes in event if it is being deleted.
private boolean mEventDeletionStarted = false;
private TextView mTitle;
private TextView mWhenDateTime;
private TextView mWhere;
private Menu mMenu = null;
private View mHeadlines;
private ScrollView mScrollView;
private View mLoadingMsgView;
private View mErrorMsgView;
private ObjectAnimator mAnimateAlpha;
private long mLoadingMsgStartTime;
private SparseIntArray mDisplayColorKeyMap = new SparseIntArray();
private int mOriginalColor = -1;
private boolean mOriginalColorInitialized = false;
private int mCalendarColor = -1;
private boolean mCalendarColorInitialized = false;
private int mCurrentColor = -1;
private boolean mCurrentColorInitialized = false;
private int mCurrentColorKey = -1;
private static final int FADE_IN_TIME = 300; // in milliseconds
private static final int LOADING_MSG_DELAY = 600; // in milliseconds
private static final int LOADING_MSG_MIN_DISPLAY_TIME = 600;
private boolean mNoCrossFade = false; // Used to prevent repeated cross-fade
private RadioGroup mResponseRadioGroup;
ArrayList<String> mToEmails = new ArrayList<String>();
ArrayList<String> mCcEmails = new ArrayList<String>();
private final Runnable mTZUpdater = new Runnable() {
@Override
public void run() {
updateEvent(mView);
}
};
private final Runnable mLoadingMsgAlphaUpdater = new Runnable() {
@Override
public void run() {
// Since this is run after a delay, make sure to only show the message
// if the event's data is not shown yet.
if (!mAnimateAlpha.isRunning() && mScrollView.getAlpha() == 0) {
mLoadingMsgStartTime = System.currentTimeMillis();
mLoadingMsgView.setAlpha(1);
}
}
};
private static int mDialogWidth = 500;
private static int mDialogHeight = 600;
private static int DIALOG_TOP_MARGIN = 8;
private boolean mIsDialog = false;
private boolean mIsPaused = true;
private boolean mDismissOnResume = false;
private int mX = -1;
private int mY = -1;
private int mMinTop; // Dialog cannot be above this location
private boolean mIsTabletConfig;
private Activity mActivity;
private Context mContext;
private CalendarController mController;
private void sendAccessibilityEventIfQueryDone(int token) {
mCurrentQuery |= token;
if (mCurrentQuery == TOKEN_QUERY_ALL) {
sendAccessibilityEvent();
}
}
public EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis,
int attendeeResponse, boolean isDialog, int windowStyle) {
Resources r = context.getResources();
if (mScale == 0) {
mScale = context.getResources().getDisplayMetrics().density;
if (mScale != 1) {
mCustomAppIconSize *= mScale;
if (isDialog) {
DIALOG_TOP_MARGIN *= mScale;
}
}
}
if (isDialog) {
setDialogSize(r);
}
mIsDialog = isDialog;
setStyle(DialogFragment.STYLE_NO_TITLE, 0);
mUri = uri;
mStartMillis = startMillis;
mEndMillis = endMillis;
mAttendeeResponseFromIntent = attendeeResponse;
mWindowStyle = windowStyle;
}
// This is currently required by the fragment manager.
public EventInfoFragment() {
}
public EventInfoFragment(Context context, long eventId, long startMillis, long endMillis,
int attendeeResponse, boolean isDialog, int windowStyle) {
this(context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis,
endMillis, attendeeResponse, isDialog, windowStyle);
mEventId = eventId;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mIsDialog) {
applyDialogParams();
}
final Activity activity = getActivity();
mContext = activity;
}
private void applyDialogParams() {
Dialog dialog = getDialog();
dialog.setCanceledOnTouchOutside(true);
Window window = dialog.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
WindowManager.LayoutParams a = window.getAttributes();
a.dimAmount = .4f;
a.width = mDialogWidth;
a.height = mDialogHeight;
// On tablets , do smart positioning of dialog
// On phones , use the whole screen
if (mX != -1 || mY != -1) {
a.x = mX - mDialogWidth / 2;
a.y = mY - mDialogHeight / 2;
if (a.y < mMinTop) {
a.y = mMinTop + DIALOG_TOP_MARGIN;
}
a.gravity = Gravity.LEFT | Gravity.TOP;
}
window.setAttributes(a);
}
public void setDialogParams(int x, int y, int minTop) {
mX = x;
mY = y;
mMinTop = minTop;
}
// Implements OnCheckedChangeListener
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
}
public void onNothingSelected(AdapterView<?> parent) {
}
@Override
public void onDetach() {
super.onDetach();
mController.deregisterEventHandler(R.layout.event_info);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = activity;
// Ensure that mIsTabletConfig is set before creating the menu.
mIsTabletConfig = Utils.getConfigBool(mActivity, R.bool.tablet_config);
mController = CalendarController.getInstance(mActivity);
mController.registerEventHandler(R.layout.event_info, this);
if (!mIsDialog) {
setHasOptionsMenu(true);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (mWindowStyle == DIALOG_WINDOW_STYLE) {
mView = inflater.inflate(R.layout.event_info_dialog, container, false);
} else {
mView = inflater.inflate(R.layout.event_info, container, false);
}
mScrollView = (ScrollView) mView.findViewById(R.id.event_info_scroll_view);
mLoadingMsgView = mView.findViewById(R.id.event_info_loading_msg);
mErrorMsgView = mView.findViewById(R.id.event_info_error_msg);
mTitle = (TextView) mView.findViewById(R.id.title);
mWhenDateTime = (TextView) mView.findViewById(R.id.when_datetime);
mWhere = (TextView) mView.findViewById(R.id.where);
mHeadlines = mView.findViewById(R.id.event_info_headline);
mResponseRadioGroup = (RadioGroup) mView.findViewById(R.id.response_value);
mAnimateAlpha = ObjectAnimator.ofFloat(mScrollView, "Alpha", 0, 1);
mAnimateAlpha.setDuration(FADE_IN_TIME);
mAnimateAlpha.addListener(new AnimatorListenerAdapter() {
int defLayerType;
@Override
public void onAnimationStart(Animator animation) {
// Use hardware layer for better performance during animation
defLayerType = mScrollView.getLayerType();
mScrollView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
// Ensure that the loading message is gone before showing the
// event info
mLoadingMsgView.removeCallbacks(mLoadingMsgAlphaUpdater);
mLoadingMsgView.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animation) {
mScrollView.setLayerType(defLayerType, null);
}
@Override
public void onAnimationEnd(Animator animation) {
mScrollView.setLayerType(defLayerType, null);
// Do not cross fade after the first time
mNoCrossFade = true;
}
});
mLoadingMsgView.setAlpha(0);
mScrollView.setAlpha(0);
mErrorMsgView.setVisibility(View.INVISIBLE);
mLoadingMsgView.postDelayed(mLoadingMsgAlphaUpdater, LOADING_MSG_DELAY);
// Hide Edit/Delete buttons if in full screen mode on a phone
if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) {
mView.findViewById(R.id.event_info_buttons_container).setVisibility(View.GONE);
}
return mView;
}
private void updateTitle() {
Resources res = getActivity().getResources();
getActivity().setTitle(res.getString(R.string.event_info_title));
}
/**
* Initializes the event cursor, which is expected to point to the first
* (and only) result from a query.
* @return false if the cursor is empty, true otherwise
*/
private boolean initEventCursor() {
if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
return false;
}
mEventCursor.moveToFirst();
mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
// mHasAlarm will be true if it was saved in the event already.
mHasAlarm = (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) == 1)? true : false;
return true;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
// Show color/edit/delete buttons only in non-dialog configuration
if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) {
inflater.inflate(R.menu.event_info_title_bar, menu);
mMenu = menu;
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// If we're a dialog we don't want to handle menu buttons
if (mIsDialog) {
return false;
}
// Handles option menu selections:
// Home button - close event info activity and start the main calendar
// one
// Edit button - start the event edit activity and close the info
// activity
// Delete button - start a delete query that calls a runnable that close
// the info activity
final int itemId = item.getItemId();
if (itemId == android.R.id.home) {
Utils.returnToCalendarHome(mContext);
mActivity.finish();
return true;
} else if (itemId == R.id.info_action_edit) {
mActivity.finish();
}
return super.onOptionsItemSelected(item);
}
@Override
public void onStop() {
super.onStop();
}
@Override
public void onDestroy() {
if (mEventCursor != null) {
mEventCursor.close();
}
if (mCalendarsCursor != null) {
mCalendarsCursor.close();
}
super.onDestroy();
}
/**
* Creates an exception to a recurring event. The only change we're making is to the
* "self attendee status" value. The provider will take care of updating the corresponding
* Attendees.attendeeStatus entry.
*
* @param eventId The recurring event.
* @param status The new value for selfAttendeeStatus.
*/
private void createExceptionResponse(long eventId, int status) {
ContentValues values = new ContentValues();
values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
values.put(Events.SELF_ATTENDEE_STATUS, status);
values.put(Events.STATUS, Events.STATUS_CONFIRMED);
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
Uri exceptionUri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
String.valueOf(eventId));
ops.add(ContentProviderOperation.newInsert(exceptionUri).withValues(values).build());
}
public static int getResponseFromButtonId(int buttonId) {
return Attendees.ATTENDEE_STATUS_NONE;
}
public static int findButtonIdForResponse(int response) {
return -1;
}
private void displayEventNotFound() {
mErrorMsgView.setVisibility(View.VISIBLE);
mScrollView.setVisibility(View.GONE);
mLoadingMsgView.setVisibility(View.GONE);
}
private void updateEvent(View view) {
if (mEventCursor == null || view == null) {
return;
}
Context context = view.getContext();
if (context == null) {
return;
}
String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
if (eventName == null || eventName.length() == 0) {
eventName = getActivity().getString(R.string.no_title_label);
}
// 3rd parties might not have specified the start/end time when firing the
// Events.CONTENT_URI intent. Update these with values read from the db.
if (mStartMillis == 0 && mEndMillis == 0) {
mStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
mEndMillis = mEventCursor.getLong(EVENT_INDEX_DTEND);
if (mEndMillis == 0) {
String duration = mEventCursor.getString(EVENT_INDEX_DURATION);
if (!TextUtils.isEmpty(duration)) {
try {
Duration d = new Duration();
d.parse(duration);
long endMillis = mStartMillis + d.getMillis();
if (endMillis >= mStartMillis) {
mEndMillis = endMillis;
} else {
Log.d(TAG, "Invalid duration string: " + duration);
}
} catch (DateException e) {
Log.d(TAG, "Error parsing duration string " + duration, e);
}
}
if (mEndMillis == 0) {
mEndMillis = mStartMillis;
}
}
}
mAllDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
mHeadlines.setBackgroundColor(mCurrentColor);
// What
if (eventName != null) {
setTextCommon(view, R.id.title, eventName);
}
// When
// Set the date and repeats (if any)
String localTimezone = Utils.getTimeZone(mActivity, mTZUpdater);
Resources resources = context.getResources();
String displayedDatetime = Utils.getDisplayedDatetime(mStartMillis, mEndMillis,
System.currentTimeMillis(), localTimezone, mAllDay, context);
String displayedTimezone = null;
if (!mAllDay) {
displayedTimezone = Utils.getDisplayedTimezone(mStartMillis, localTimezone,
eventTimezone);
}
// Display the datetime. Make the timezone (if any) transparent.
if (displayedTimezone == null) {
setTextCommon(view, R.id.when_datetime, displayedDatetime);
} else {
int timezoneIndex = displayedDatetime.length();
displayedDatetime += " " + displayedTimezone;
SpannableStringBuilder sb = new SpannableStringBuilder(displayedDatetime);
ForegroundColorSpan transparentColorSpan = new ForegroundColorSpan(
resources.getColor(R.color.event_info_headline_transparent_color));
sb.setSpan(transparentColorSpan, timezoneIndex, displayedDatetime.length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
setTextCommon(view, R.id.when_datetime, sb);
}
view.findViewById(R.id.when_repeat).setVisibility(View.GONE);
// Organizer view is setup in the updateCalendar method
// Where
if (location == null || location.trim().length() == 0) {
setVisibilityCommon(view, R.id.where, View.GONE);
} else {
final TextView textView = mWhere;
if (textView != null) {
textView.setText(location.trim());
}
}
// Launch Custom App
if (Utils.isJellybeanOrLater()) {
updateCustomAppButton();
}
}
private void updateCustomAppButton() {
setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
return;
}
private void sendAccessibilityEvent() {
AccessibilityManager am =
(AccessibilityManager) getActivity().getSystemService(Service.ACCESSIBILITY_SERVICE);
if (!am.isEnabled()) {
return;
}
AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
event.setClassName(EventInfoFragment.class.getName());
event.setPackageName(getActivity().getPackageName());
List<CharSequence> text = event.getText();
if (mResponseRadioGroup.getVisibility() == View.VISIBLE) {
int id = mResponseRadioGroup.getCheckedRadioButtonId();
if (id != View.NO_ID) {
text.add(((TextView) getView().findViewById(R.id.response_label)).getText());
text.add((((RadioButton) (mResponseRadioGroup.findViewById(id)))
.getText() + PERIOD_SPACE));
}
}
am.sendAccessibilityEvent(event);
}
private void updateCalendar(View view) {
mCalendarOwnerAccount = "";
if (mCalendarsCursor != null && mEventCursor != null) {
mCalendarsCursor.moveToFirst();
String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount;
mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0;
mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME);
setVisibilityCommon(view, R.id.organizer_container, View.GONE);
mIsBusyFreeCalendar =
mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.CAL_ACCESS_FREEBUSY;
if (!mIsBusyFreeCalendar) {
View b = mView.findViewById(R.id.edit);
b.setEnabled(true);
b.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// For dialogs, just close the fragment
// For full screen, close activity on phone, leave it for tablet
if (mIsDialog) {
EventInfoFragment.this.dismiss();
}
else if (!mIsTabletConfig){
getActivity().finish();
}
}
});
}
View button;
if ((!mIsDialog && !mIsTabletConfig ||
mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) && mMenu != null) {
mActivity.invalidateOptionsMenu();
}
} else {
setVisibilityCommon(view, R.id.calendar, View.GONE);
sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS);
}
}
private void setTextCommon(View view, int id, CharSequence text) {
TextView textView = (TextView) view.findViewById(id);
if (textView == null)
return;
textView.setText(text);
}
private void setVisibilityCommon(View view, int id, int visibility) {
View v = view.findViewById(id);
if (v != null) {
v.setVisibility(visibility);
}
return;
}
@Override
public void onPause() {
mIsPaused = true;
super.onPause();
}
@Override
public void onResume() {
super.onResume();
if (mIsDialog) {
setDialogSize(getActivity().getResources());
applyDialogParams();
}
mIsPaused = false;
if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
int buttonId = findButtonIdForResponse(mTentativeUserSetResponse);
mResponseRadioGroup.check(buttonId);
}
}
@Override
public void eventsChanged() {
}
@Override
public long getSupportedEventTypes() {
return EventType.EVENTS_CHANGED;
}
@Override
public void handleEvent(EventInfo event) {
reloadEvents();
}
public void reloadEvents() {
}
@Override
public void onClick(View view) {
}
public long getEventId() {
return mEventId;
}
public long getStartMillis() {
return mStartMillis;
}
public long getEndMillis() {
return mEndMillis;
}
private void setDialogSize(Resources r) {
mDialogWidth = (int)r.getDimension(R.dimen.event_info_dialog_width);
mDialogHeight = (int)r.getDimension(R.dimen.event_info_dialog_height);
}
}