/* * 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 mToEmails = new ArrayList(); ArrayList mCcEmails = new ArrayList(); 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 ops = new ArrayList(); 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 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); } }