| /* |
| * 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; // nonessential value |
| EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_URI] = Events._ID; // nonessential 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); |
| } |
| } |