Commit d845fbe5 authored by Erik's avatar Erik
Browse files

New Edit Event layout

Some large changes to EditEvent including a new two-pane layout,
an attendance widget, and a list of attendees with remove buttons.
This also removes a lot of the excess layout code and cleans up
the theme code.

Change-Id: I87ab3511f7bb6501f2aa4bf6d33b5d4f8e6f4936
parent b61402e2
......@@ -65,7 +65,7 @@
</activity-alias>
<activity android:name="EditEventActivity" android:label="@string/event_edit_title"
android:theme="@android:style/Theme"
android:theme="@android:style/Theme.Light.Holo"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
......
......@@ -18,7 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:paddingLeft="0dip"
android:paddingRight="9dip"
android:paddingRight="0dip"
android:minHeight="48dip">
<QuickContactBadge
......@@ -37,25 +37,35 @@
android:src="@drawable/ic_contact_picture"
style="?android:attr/quickContactBadgeStyleWindowMedium" />
<TextView
android:id="@+id/name"
android:textAppearance="?android:attr/textAppearanceSmall"
android:inputType="none"
android:paddingLeft="2dip"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/badge"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/presence"
android:scaleType="fitXY"
android:visibility="gone"
android:layout_alignParentRight="true"
android:layout_toRightOf="@id/badge"
android:layout_centerVertical="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageButton android:id="@+id/contact_remove"
style="@style/MinusButton"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true" />
<TextView
android:id="@+id/name"
android:textAppearance="?android:attr/textAppearanceMedium"
android:inputType="none"
android:paddingLeft="2dip"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/presence"
android:layout_toLeftOf="@id/contact_remove"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.EditEvent_Label"/>
<View
android:id="@+id/separator"
android:layout_width="match_parent"
......
This diff is collapsed.
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml
**
** Copyright 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.
*/
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
style="?android:attr/dropDownItemStyle"
android:textAppearance="?android:attr/textAppearanceLarge"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:ellipsize="marquee" />
......@@ -54,24 +54,21 @@
<style name="TextAppearance.AgendaView_ValueLabel">
<item name="android:textSize">14sp</item>
<item name="android:textColor">@android:color/black</item>
</style>
<style name="TextAppearance.EditEvent_Label">
<item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
<item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
<item name="android:textStyle">bold</item>
<item name="android:paddingLeft">2dip</item>
</style>
<style name="TextAppearance.EventInfo_Label">
<item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textStyle">bold</item>
<item name="android:paddingLeft">2dip</item>
</style>
<style name="EditEvent_Layout">
<style name="EditEvent_Layout" parent="android:Theme.Light.Holo">
<item name="android:paddingLeft">6dip</item>
<item name="android:paddingRight">7dip</item>
<item name="android:paddingTop">8dip</item>
......@@ -79,18 +76,15 @@
<style name="TextAppearance.Alert_Title">
<item name="android:textSize">18sp</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="TextAppearance.Alert_Label">
<item name="android:textSize">14sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="TextAppearance.Alert_Value">
<item name="android:textSize">14sp</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="CalendarTheme" parent="android:Theme.Light.NoTitleBar">
......@@ -105,8 +99,7 @@
<style name="MultiStateButton">
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item>
<item name="android:textColor">@android:color/primary_text_light</item>
<item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
<item name="android:gravity">center_vertical|left</item>
</style>
......
......@@ -20,10 +20,12 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.provider.Calendar.Attendees;
import android.provider.Calendar.Events;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.TimeZone;
/**
......@@ -32,6 +34,41 @@ import java.util.TimeZone;
* the events table. Only fields that are important to the UI are included.
*/
public class CalendarEventModel {
public static class Attendee {
@Override
public int hashCode() {
return (mEmail == null) ? 0 : mEmail.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Attendee)) {
return false;
}
Attendee other = (Attendee) obj;
if (!TextUtils.equals(mEmail, other.mEmail)) {
return false;
}
return true;
}
public String mName;
public String mEmail;
public int mStatus;
public Attendee(String name, String email) {
mName = name;
mEmail = email;
mStatus = Attendees.ATTENDEE_STATUS_NONE;
}
}
// TODO strip out fields that don't ever get used
/**
* The uri of the event in the db. This should only be null for new events.
......@@ -73,6 +110,7 @@ public class CalendarEventModel {
// as an attendee by default.
public boolean mHasAttendeeData = true;
public int mSelfAttendeeStatus = -1;
public int mOwnerAttendeeId = -1;
public String mOriginalEvent = null;
public Long mOriginalTime = null;
public Boolean mOriginalAllDay = null;
......@@ -85,13 +123,12 @@ public class CalendarEventModel {
// PROVIDER_NOTES Using EditEventHelper the owner should not be included in this
// list and will instead be added by saveEvent. Is this what we want?
public String mAttendees;
public LinkedHashMap<String, Attendee> mAttendeesList;
public CalendarEventModel() {
mReminderMinutes = new ArrayList<Integer>();
mAttendees = "";
mAttendeesList = new LinkedHashMap<String, Attendee>();
mTimezone = TimeZone.getDefault().getID();
}
public CalendarEventModel(Context context) {
......@@ -197,6 +234,7 @@ public class CalendarEventModel {
mHasAttendeeData = true;
mSelfAttendeeStatus = -1;
mOwnerAttendeeId = -1;
mOriginalEvent = null;
mOriginalTime = null;
mOriginalAllDay = null;
......@@ -207,7 +245,28 @@ public class CalendarEventModel {
mVisibility = 0;
mReminderMinutes = new ArrayList<Integer>();
mAttendees = "";
mAttendeesList.clear();
}
public void addAttendee(Attendee attendee) {
mAttendeesList.put(attendee.mEmail, attendee);
}
public void removeAttendee(Attendee attendee) {
mAttendeesList.remove(attendee.mEmail);
}
public String getAttendeesString() {
StringBuilder b = new StringBuilder();
for (Attendee attendee : mAttendeesList.values()) {
String name = attendee.mName;
String email = attendee.mEmail;
String status = Integer.toString(attendee.mStatus);
b.append("name:").append(name);
b.append(" email:").append(email);
b.append(" status:").append(status);
}
return b.toString();
}
@Override
......@@ -215,7 +274,7 @@ public class CalendarEventModel {
final int prime = 31;
int result = 1;
result = prime * result + (mAllDay ? 1231 : 1237);
result = prime * result + ((mAttendees == null) ? 0 : mAttendees.hashCode());
result = prime * result + ((mAttendeesList == null) ? 0 : getAttendeesString().hashCode());
result = prime * result + (int) (mCalendarId ^ (mCalendarId >>> 32));
result = prime * result + ((mDescription == null) ? 0 : mDescription.hashCode());
result = prime * result + ((mDuration == null) ? 0 : mDuration.hashCode());
......@@ -239,6 +298,7 @@ public class CalendarEventModel {
result = prime * result + ((mReminderMinutes == null) ? 0 : mReminderMinutes.hashCode());
result = prime * result + ((mRrule == null) ? 0 : mRrule.hashCode());
result = prime * result + mSelfAttendeeStatus;
result = prime * result + mOwnerAttendeeId;
result = prime * result + (int) (mStart ^ (mStart >>> 32));
result = prime * result + ((mSyncAccount == null) ? 0 : mSyncAccount.hashCode());
result = prime * result + ((mSyncAccountType == null) ? 0 : mSyncAccountType.hashCode());
......@@ -269,11 +329,11 @@ public class CalendarEventModel {
if (mAllDay != other.mAllDay) {
return false;
}
if (mAttendees == null) {
if (other.mAttendees != null) {
if (mAttendeesList == null) {
if (other.mAttendeesList != null) {
return false;
}
} else if (!mAttendees.equals(other.mAttendees)) {
} else if (!TextUtils.equals(getAttendeesString(), other.getAttendeesString())) {
return false;
}
......@@ -400,6 +460,9 @@ public class CalendarEventModel {
if (mSelfAttendeeStatus != other.mSelfAttendeeStatus) {
return false;
}
if (mOwnerAttendeeId != other.mOwnerAttendeeId) {
return false;
}
if (mStart != other.mStart) {
return false;
}
......
......@@ -16,6 +16,8 @@
package com.android.calendar;
import com.android.calendar.event.EditEventHelper.AttendeeItem;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
......@@ -53,6 +55,7 @@ public class ContactsAsyncHelper extends Handler {
// constants
private static final int EVENT_LOAD_IMAGE = 1;
private static final int EVENT_LOAD_DRAWABLE = 2;
private static final int DEFAULT_TOKEN = -1;
// static objects
......@@ -69,6 +72,8 @@ public class ContactsAsyncHelper extends Handler {
public Uri uri;
public int defaultResource;
public Object result;
public AttendeeItem item;
public Runnable callback;
}
/**
......@@ -85,6 +90,7 @@ public class ContactsAsyncHelper extends Handler {
WorkerArgs args = (WorkerArgs) msg.obj;
switch (msg.arg1) {
case EVENT_LOAD_DRAWABLE:
case EVENT_LOAD_IMAGE:
InputStream inputStream = null;
try {
......@@ -175,6 +181,43 @@ public class ContactsAsyncHelper extends Handler {
sThreadHandler.sendMessage(msg);
}
/**
* Start an image load, attach the result to the specified CallerInfo object.
* Note, when the query is started, we make the ImageView INVISIBLE if the
* placeholderImageResource value is -1. When we're given a valid (!= -1)
* placeholderImageResource value, we make sure the image is visible.
*/
public static final void retrieveContactPhotoAsync(Context context,
AttendeeItem item, Runnable run, Uri person) {
// in case the source caller info is null, the URI will be null as well.
// just return as there's nothing to do.
if (person == null) {
return;
}
// Added additional Cookie field in the callee to handle arguments
// sent to the callback function.
// setup arguments
WorkerArgs args = new WorkerArgs();
args.context = context;
args.item = item;
args.uri = person;
args.callback = run;
// setup message arguments
Message msg = sThreadHandler.obtainMessage(DEFAULT_TOKEN);
msg.arg1 = EVENT_LOAD_DRAWABLE;
msg.obj = args;
if (DBG) Log.d(LOG_TAG, "Begin loading drawable: " + args.uri);
// notify the thread to begin working
sThreadHandler.sendMessage(msg);
}
/**
* Called when loading is done.
*/
......@@ -183,19 +226,24 @@ public class ContactsAsyncHelper extends Handler {
WorkerArgs args = (WorkerArgs) msg.obj;
switch (msg.arg1) {
case EVENT_LOAD_IMAGE:
boolean imagePresent = false;
// if the image has been loaded then display it, otherwise set default.
// in either case, make sure the image is visible.
if (args.result != null) {
args.view.setVisibility(View.VISIBLE);
args.view.setImageDrawable((Drawable) args.result);
imagePresent = true;
} else if (args.defaultResource != -1) {
args.view.setVisibility(View.VISIBLE);
args.view.setImageResource(args.defaultResource);
}
break;
case EVENT_LOAD_DRAWABLE:
if (args.result != null) {
args.item.mBadge = (Drawable) args.result;
if (args.callback != null) {
args.callback.run();
}
}
break;
default:
}
}
......
......@@ -20,9 +20,9 @@ import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.text.util.Rfc822Token;
import android.view.View;
import android.widget.ResourceCursorAdapter;
......@@ -46,7 +46,7 @@ public class EmailAddressAdapter extends ResourceCursorAdapter {
};
public EmailAddressAdapter(Context context) {
super(context, android.R.layout.simple_dropdown_item_1line, null);
super(context, R.layout.simple_dropdown_item_1line, null);
mContentResolver = context.getContentResolver();
}
......
......@@ -30,7 +30,6 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
......@@ -39,20 +38,19 @@ import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.pim.EventRecurrence;
import android.provider.ContactsContract;
import android.provider.Calendar.Attendees;
import android.provider.Calendar.Calendars;
import android.provider.Calendar.Events;
import android.provider.Calendar.Reminders;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.Presence;
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
......@@ -60,7 +58,6 @@ import android.text.format.Time;
import android.text.util.Linkify;
import android.text.util.Rfc822Token;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
......@@ -87,7 +84,7 @@ import java.util.regex.Pattern;
public class EventInfoFragment extends Fragment implements View.OnClickListener,
AdapterView.OnItemSelectedListener {
public static final boolean DEBUG = true;
public static final boolean DEBUG = false;
public static final String TAG = "EventInfoActivity";
......@@ -200,15 +197,6 @@ public class EventInfoFragment extends Fragment implements View.OnClickListener,
private static final int MENU_EDIT = 2;
private static final int MENU_DELETE = 3;
private static final int ATTENDEE_ID_NONE = -1;
private static final int ATTENDEE_NO_RESPONSE = -1;
private static final int[] ATTENDEE_VALUES = {
ATTENDEE_NO_RESPONSE,
Attendees.ATTENDEE_STATUS_ACCEPTED,
Attendees.ATTENDEE_STATUS_TENTATIVE,
Attendees.ATTENDEE_STATUS_DECLINED,
};
private View mView;
private LinearLayout mRemindersContainer;
private LinearLayout mOrganizerContainer;
......@@ -225,7 +213,7 @@ public class EventInfoFragment extends Fragment implements View.OnClickListener,
private boolean mHasAttendeeData;
private boolean mIsOrganizer;
private long mCalendarOwnerAttendeeId = ATTENDEE_ID_NONE;
private long mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE;
private boolean mOrganizerCanRespond;
private String mCalendarOwnerAccount;
private boolean mCanModifyCalendar;
......@@ -245,7 +233,7 @@ public class EventInfoFragment extends Fragment implements View.OnClickListener,
private int mResponseOffset;
private int mOriginalAttendeeResponse;
private int mAttendeeResponseFromIntent = ATTENDEE_NO_RESPONSE;
private int mAttendeeResponseFromIntent = EditEventHelper.ATTENDEE_NO_RESPONSE;
private boolean mIsRepeating;
private boolean mIsDuplicateName;
......@@ -547,8 +535,8 @@ public class EventInfoFragment extends Fragment implements View.OnClickListener,
@SuppressWarnings("fallthrough")
private void initAttendeesCursor(View view) {
mOriginalAttendeeResponse = ATTENDEE_NO_RESPONSE;
mCalendarOwnerAttendeeId = ATTENDEE_ID_NONE;
mOriginalAttendeeResponse = EditEventHelper.ATTENDEE_NO_RESPONSE;
mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE;
mNumOfAttendees = 0;
if (mAttendeesCursor != null) {
mNumOfAttendees = mAttendeesCursor.getCount();
......@@ -573,7 +561,7 @@ public class EventInfoFragment extends Fragment implements View.OnClickListener,
}
}
if (mCalendarOwnerAttendeeId == ATTENDEE_ID_NONE &&
if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE &&
mCalendarOwnerAccount.equalsIgnoreCase(email)) {
mCalendarOwnerAttendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID);
mOriginalAttendeeResponse = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
......@@ -767,7 +755,7 @@ public class EventInfoFragment extends Fragment implements View.OnClickListener,
return false;
}
int status = ATTENDEE_VALUES[position];
int status = EditEventHelper.ATTENDEE_VALUES[position];
// If the status has not changed, then don't update the database
if (status == mOriginalAttendeeResponse) {
......@@ -775,7 +763,7 @@ public class EventInfoFragment extends Fragment implements View.OnClickListener,
}
// If we never got an owner attendee id we can't set the status
if (mCalendarOwnerAttendeeId == ATTENDEE_ID_NONE) {
if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE) {
return false;
}
......@@ -852,9 +840,9 @@ public class EventInfoFragment extends Fragment implements View.OnClickListener,
}
private int findResponseIndexFor(int response) {
int size = ATTENDEE_VALUES.length;
int size = EditEventHelper.ATTENDEE_VALUES.length;
for (int index = 0; index < size; index++) {
if (ATTENDEE_VALUES[index] == response) {
if (EditEventHelper.ATTENDEE_VALUES[index] == response) {
return index;
}
}
......@@ -1181,7 +1169,7 @@ public class EventInfoFragment extends Fragment implements View.OnClickListener,
* no response option.
*/
if ((mOriginalAttendeeResponse != Attendees.ATTENDEE_STATUS_INVITED)
&& (mOriginalAttendeeResponse != ATTENDEE_NO_RESPONSE)
&& (mOriginalAttendeeResponse != EditEventHelper.ATTENDEE_NO_RESPONSE)
&& (mOriginalAttendeeResponse != Attendees.ATTENDEE_STATUS_NONE)) {
CharSequence[] entries;
entries = getActivity().getResources().getTextArray(R.array.response_labels2);
......@@ -1194,7 +1182,7 @@ public class EventInfoFragment extends Fragment implements View.OnClickListener,
}
int index;
if (mAttendeeResponseFromIntent != ATTENDEE_NO_RESPONSE) {
if (mAttendeeResponseFromIntent != EditEventHelper.ATTENDEE_NO_RESPONSE) {
index = findResponseIndexFor(mAttendeeResponseFromIntent);
} else {
index = findResponseIndexFor(mOriginalAttendeeResponse);
......
/*
* 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.event;
import com.android.calendar.CalendarEventModel.Attendee;
import com.android.calendar.ContactsAsyncHelper;
import com.android.calendar.R;
import com.android.calendar.event.EditEventHelper.AttendeeItem;
import com.android.common.Rfc822Validator;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.Calendar.Attendees;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.Presence;
import android.provider.ContactsContract.QuickContact;
import android.text.TextUtils;
import android.text.util.Rfc822Token;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.QuickContactBadge;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
public class AttendeesAdapter extends BaseAdapter {
private static final String TAG = "AttendeesAdapter";
private static final boolean DEBUG = false;
int PRESENCE_PROJECTION_CONTACT_ID_INDEX = 0;
int PRESENCE_PROJECTION_PRESENCE_INDEX = 1;
int PRESENCE_PROJECTION_EMAIL_INDEX = 2;
int PRESENCE_PROJECTION_PHOTO_ID_INDEX = 3;
private static final String[] PRESENCE_PROJECTION = new String[] {
Email.CONTACT_ID, // 0
Email.CONTACT_PRESENCE, // 1
Email.DATA, // 2
Email.PHOTO_ID, // 3
};
private static final Uri CONTACT_DATA_WITH_PRESENCE_URI = Data.CONTENT_URI;
private static final String CONTACT_DATA_SELECTION = Email.DATA + " IN (?)";
private Context mContext;
private Rfc822Validator mValidator;
private LayoutInflater mInflater;
private int mYes = 0;
private int mNo = 0;
private int mMaybe = 0;
private int mNoResponse = 0;
private Drawable mDefaultBadge;
private ArrayList<AttendeeItem> mAttendees;
private AttendeeItem[] mDividers = new AttendeeItem[4];
private RemoveAttendeeClickListener mRemoveListener = new RemoveAttendeeClickListener();
private PresenceQueryHandler mPresenceQueryHandler;
private class RemoveAttendeeClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
AttendeeItem item = (AttendeeItem) v.getTag();
item.mRemoved = !item.mRemoved;
notifyDataSetChanged();
}
}
public AttendeesAdapter(Context context, Rfc822Validator validator) {
Resources res = context.getResources();
mContext = context;
mValidator = validator;
mAttendees = new ArrayList<AttendeeItem>();
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mDefaultBadge = res.getDrawable(R.drawable.ic_contact_picture);
// Add the labels we need to our adapter
CharSequence[] entries;
entries = res.getTextArray(R.array.response_labels1);
// Yes
mDividers[0] = addDivider(context, entries[1]);
// No
mDividers[1] = addDivider(context, entries[3]);
// Maybe
mDividers[2] = addDivider(context, entries[2]);
// No Response
mDividers[3] = addDivider(context, entries[0]);
mPresenceQueryHandler = new PresenceQueryHandler(context.getContentResolver());
}
private AttendeeItem addDivider(Context context, CharSequence label) {
AttendeeItem labelItem = new AttendeeItem();
labelItem.mRemoved = true;
labelItem.mDivider = true;
labelItem.mDividerLabel = label.toString();
mAttendees.add(labelItem);
return labelItem;
}
public boolean contains(Attendee attendee) {
for (AttendeeItem item : mAttendees) {
if (item.mAttendee == null) {
continue;
}
if(TextUtils.equals(attendee.mEmail, item.mAttendee.mEmail)) {
return true;
}
}
return false;
}
private void addAttendee(Attendee attendee) {
int i = 0;
if (contains(attendee)) {
return;
}
int status = attendee.mStatus;
AttendeeItem startItem;
if (status == Attendees.ATTENDEE_STATUS_ACCEPTED) {
startItem = mDividers[0];
mYes++;
} else if (status == Attendees.ATTENDEE_STATUS_DECLINED) {
startItem = mDividers[1];
mNo++;
} else if (status == Attendees.ATTENDEE_STATUS_TENTATIVE){
startItem = mDividers[2];
mMaybe++;
} else {
startItem = mDividers[3];
mNoResponse++;
}
// Advance to the start of the section this name should go in
while (mAttendees.get(i++) != startItem);
int size = mAttendees.size();
String name = attendee.mName;
if (name == null) {
name = "";
}
while (true) {
if (i >= size) {
break;
}
AttendeeItem currItem = mAttendees.get(i);
if (currItem.mDivider) {
break;
}
if (name.compareToIgnoreCase(currItem.mAttendee.mName) < 0) {
break;
}
i++;
}
AttendeeItem item = new AttendeeItem();
item.mAttendee = attendee;
item.mPresence = -1;
item.mBadge = mDefaultBadge;
mAttendees.add(i, item);
// If we have any yes or no responses turn labels on where necessary
if (mYes > 0 || mNo > 0 || mMaybe > 0) {
startItem.mRemoved = false; // mView.setVisibility(View.VISIBLE);
if (DEBUG) {
Log.d(TAG, "Set " + startItem.mDividerLabel + " to visible");
}
if (mNoResponse > 0) {
mDividers[3].mRemoved = false; // mView.setVisibility(View.VISIBLE);
if (DEBUG) {
Log.d(TAG, "Set " + mDividers[2].mDividerLabel + " to visible");
}
}
}
mPresenceQueryHandler.startQuery(item.mUpdateCounts + 1, item,
CONTACT_DATA_WITH_PRESENCE_URI, PRESENCE_PROJECTION, CONTACT_DATA_SELECTION,
new String[] { attendee.mEmail }, null);
notifyDataSetChanged();
}
public void addAttendees(ArrayList<Attendee> attendees) {
synchronized (mAttendees) {
for (Attendee attendee : attendees) {
addAttendee(attendee);
}
}
}
public void addAttendees(HashMap<String, Attendee> attendees) {
synchronized (mAttendees) {
for (Attendee attendee : attendees.values()) {
addAttendee(attendee);
}
}
}
public void addAttendees(String attendees) {
LinkedHashSet<Rfc822Token> addresses =
EditEventHelper.getAddressesFromList(attendees, mValidator);
synchronized (mAttendees) {
for (Rfc822Token address : addresses) {
Attendee attendee = new Attendee(address.getName(), address.getAddress());
if (TextUtils.isEmpty(attendee.mName)) {
attendee.mName = attendee.mEmail;
}
addAttendee(attendee);
}
}
}
public void removeAttendee(int position) {
if (position < 0) {
return;
}
AttendeeItem item = mAttendees.get(position);
if (item.mDivider) {
return;
}
int status = item.mAttendee.mStatus;
if (status == Attendees.ATTENDEE_STATUS_ACCEPTED) {
mYes--;
if (mYes == 0) {
mDividers[0].mRemoved = true;
}
} else if (status == Attendees.ATTENDEE_STATUS_DECLINED) {
mNo--;
if (mNo == 0) {
mDividers[1].mRemoved = true;
}
} else if (status == Attendees.ATTENDEE_STATUS_TENTATIVE) {
mMaybe--;
} else {
mNoResponse--;
}
if ((mYes == 0 && mNo == 0 && mMaybe == 0) || mNoResponse == 0) {
mDividers[3].mRemoved = true;
}
mAttendees.remove(position);
}
public int findAttendeeByEmail(String email) {
int size = mAttendees.size();
for (int i = 0; i < size; i++) {
AttendeeItem item = mAttendees.get(i);
if (item.mDivider) {
continue;
}
if (TextUtils.equals(email, item.mAttendee.mEmail)) {
return i;
}
}
return -1;
}
@Override
public int getCount() {
return mAttendees.size();
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public Attendee getItem(int position) {
return mAttendees.get(position).mAttendee;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
AttendeeItem item = mAttendees.get(position);
if (item.mDivider) {
TextView tv = new TextView(mContext);
tv.setText(item.mDividerLabel);
tv.setTextAppearance(mContext, R.style.TextAppearance_EventInfo_Label);
tv.setVisibility(item.mRemoved ? View.GONE : View.VISIBLE);
return tv;
}
View v = mInflater.inflate(R.layout.contact_item, null);
Attendee attendee = item.mAttendee;
TextView nameView = (TextView)v.findViewById(R.id.name);
String name = attendee.mName;
if (name == null || name.length() == 0) {
name = attendee.mEmail;
}
nameView.setText(name);
if (item.mRemoved) {
nameView.setPaintFlags(Paint.STRIKE_THRU_TEXT_FLAG | nameView.getPaintFlags());
}
ImageButton button = (ImageButton) v.findViewById(R.id.contact_remove);
button.setVisibility(View.VISIBLE);
button.setTag(item);
if (item.mRemoved) {
button.setImageResource(R.drawable.ic_btn_round_plus);
}
button.setOnClickListener(mRemoveListener);
QuickContactBadge badge = (QuickContactBadge)v.findViewById(R.id.badge);
badge.setImageDrawable(item.mBadge);
badge.assignContactFromEmail(item.mAttendee.mEmail, true);
if (item.mPresence != -1) {
ImageView presence = (ImageView) v.findViewById(R.id.presence);
presence.setImageResource(Presence.getPresenceIconResourceId(item.mPresence));
presence.setVisibility(View.VISIBLE);
}
return v;
}
@Override
public boolean isEnabled(int position) {
return !mAttendees.get(position).mDivider;
}
/**
* Taken from com.google.android.gm.HtmlConversationActivity
*
* Send the intent that shows the Contact info corresponding to the email address.
*/
public void showContactInfo(Attendee attendee, Rect rect) {
// First perform lookup query to find existing contact
final ContentResolver resolver = mContext.getContentResolver();
final String address = attendee.mEmail;
final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI,
Uri.encode(address));
final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri);
if (lookupUri != null) {
// Found matching contact, trigger QuickContact
QuickContact.showQuickContact(mContext, rect, lookupUri,
QuickContact.MODE_MEDIUM, null);
} else {
// No matching contact, ask user to create one
final Uri mailUri = Uri.fromParts("mailto", address, null);
final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri);
// Pass along full E-mail string for possible create dialog
Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null);
intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString());
// Only provide personal name hint if we have one
final String senderPersonal = attendee.mName;
if (!TextUtils.isEmpty(senderPersonal)) {
intent.putExtra(Intents.Insert.NAME, senderPersonal);
}
mContext.startActivity(intent);
}
}
public boolean isRemoved(int position) {
return mAttendees.get(position).mRemoved;
}
// TODO put this into a Loader for auto-requeries
private class PresenceQueryHandler extends AsyncQueryHandler {
public PresenceQueryHandler(ContentResolver cr) {
super(cr);
}
@Override
protected void onQueryComplete(int queryIndex, Object cookie, Cursor cursor) {
if (cursor == null || cookie == null) {
if (DEBUG) {
Log.d(TAG, "onQueryComplete: cursor=" + cursor + ", cookie=" + cookie);
}
return;
}
AttendeeItem item = (AttendeeItem)cookie;
try {
cursor.moveToPosition(-1);
boolean found = false;
int contactId = 0;
int photoId = 0;
int presence = 0;
while (cursor.moveToNext()) {
String email = cursor.getString(PRESENCE_PROJECTION_EMAIL_INDEX);
int temp = 0;
temp = cursor.getInt(PRESENCE_PROJECTION_PHOTO_ID_INDEX);
// A photo id must be > 0 and we only care about the contact
// ID if there's a photo
if (temp > 0) {
photoId = temp;
contactId = cursor.getInt(PRESENCE_PROJECTION_CONTACT_ID_INDEX);
}
// Take the most available status we can find.
presence = Math.max(
cursor.getInt(PRESENCE_PROJECTION_PRESENCE_INDEX), presence);
found = true;
if (DEBUG) {
Log.d(TAG,
"onQueryComplete Id: " + contactId + " PhotoId: " + photoId
+ " Email: " + email + " updateCount:" + item.mUpdateCounts
+ " Presence:" + item.mPresence);
}
}
if (found) {
item.mPresence = presence;
notifyDataSetChanged();
if (photoId > 0 && item.mUpdateCounts < queryIndex) {
item.mUpdateCounts = queryIndex;
Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
contactId);
// Query for this contacts picture
ContactsAsyncHelper.retrieveContactPhotoAsync(
mContext, item, new Runnable() {
public void run() {
notifyDataSetChanged();
}
}, personUri);
}
}
} finally {
cursor.close();
}
}
}
}
......@@ -21,6 +21,7 @@ import static android.provider.Calendar.EVENT_END_TIME;
import com.android.calendar.AbstractCalendarActivity;
import com.android.calendar.CalendarEventModel;
import com.android.calendar.CalendarEventModel.Attendee;
import com.android.calendar.DeleteEventHelper;
import com.android.calendar.R;
import com.android.calendar.Utils;
......@@ -121,8 +122,12 @@ public class EditEventFragment extends Fragment {
displayEditWhichDialogue();
// Reminders cursor
eventId = mModel.mId;
// If there are attendees or alarms query for them
// We only query one table at a time so that we can easily
// tell if we are finished with all our queries. At a later
// point we might want to parallelize this and keep track of
// which queries are done.
if (mModel.mHasAttendeeData && eventId != -1) {
Uri attUri = Attendees.CONTENT_URI;
String[] whereArgs = {
......@@ -130,8 +135,19 @@ public class EditEventFragment extends Fragment {
};
mHandler.startQuery(TOKEN_ATTENDEES, null, attUri,
EditEventHelper.ATTENDEES_PROJECTION,
EditEventHelper.ATTENDEES_WHERE_NOT_ORGANIZER /* selection */,
EditEventHelper.ATTENDEES_WHERE /* selection */,
whereArgs /* selection args */, null /* sort order */);
} else if (mModel.mHasAlarm) {
Uri rUri = Reminders.CONTENT_URI;
String[] remArgs = {
Long.toString(eventId), Integer.toString(Reminders.METHOD_ALERT),
Integer.toString(Reminders.METHOD_DEFAULT)
};
mHandler
.startQuery(TOKEN_REMINDERS, null, rUri,
EditEventHelper.REMINDERS_PROJECTION,
EditEventHelper.REMINDERS_WHERE /* selection */,
remArgs /* selection args */, null /* sort order */);
} else {
// Set the model if there are no more queries to
// make
......@@ -140,25 +156,31 @@ public class EditEventFragment extends Fragment {
break;
case TOKEN_ATTENDEES:
try {
StringBuilder b = new StringBuilder();
while (cursor.moveToNext()) {
String name = cursor.getString(EditEventHelper.ATTENDEES_INDEX_NAME);
String email = cursor.getString(EditEventHelper.ATTENDEES_INDEX_EMAIL);
int status = cursor.getInt(EditEventHelper.ATTENDEES_INDEX_STATUS);
int relationship = cursor
.getInt(EditEventHelper.ATTENDEES_INDEX_RELATIONSHIP);
if (email != null) {
if (name != null && name.length() > 0 && !name.equals(email)) {
b.append('"').append(name).append("\" ");
}
b.append('<').append(email).append(">, ");
if (relationship == Attendees.RELATIONSHIP_ORGANIZER) {
mModel.mOrganizer = email;
}
if (mModel.mOwnerAccount != null &&
mModel.mOwnerAccount.equalsIgnoreCase(email)) {
int attendeeId =
cursor.getInt(EditEventHelper.ATTENDEES_INDEX_ID);
mModel.mOwnerAttendeeId = attendeeId;
mModel.mSelfAttendeeStatus = status;
mOriginalModel.mOwnerAttendeeId = attendeeId;
mOriginalModel.mSelfAttendeeStatus = status;
continue;
}
}
}
if (b.length() > 0) {
mModel.mAttendees = b.toString();
mOriginalModel.mAttendees = new String(mModel.mAttendees);
Attendee attendee = new Attendee(name, email);
attendee.mStatus = status;
mModel.addAttendee(attendee);
mOriginalModel.addAttendee(attendee);
}
} finally {
cursor.close();
......@@ -177,10 +199,7 @@ public class EditEventFragment extends Fragment {
.startQuery(TOKEN_REMINDERS, null, rUri,
EditEventHelper.REMINDERS_PROJECTION,
EditEventHelper.REMINDERS_WHERE /* selection */,
remArgs /* selection args */, null /*
* sort
* order
*/);
remArgs /* selection args */, null /* sort order */);
} else {
// Set the model if there are no more queries to
// make
......@@ -254,6 +273,7 @@ public class EditEventFragment extends Fragment {
}
mModel.mStart = mBegin;
mModel.mEnd = mEnd;
mModel.mSelfAttendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED;
mView.setModel(mModel);
// Start a query in the background to read the list of calendars
......@@ -383,7 +403,8 @@ public class EditEventFragment extends Fragment {
}).setTitle(R.string.edit_event_label).setItems(items, new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
mModification = (mModel.mSyncId == null) ? Utils.MODIFY_ALL : Utils.MODIFY_SELECTED;
mModification = (mModel.mSyncId == null) ? Utils.MODIFY_ALL
: Utils.MODIFY_SELECTED;
} else if (which == 1) {
mModification = (mModel.mSyncId == null) ? Utils.MODIFY_ALL_FOLLOWING
: Utils.MODIFY_ALL;
......
......@@ -19,6 +19,7 @@ package com.android.calendar.event;
import com.android.calendar.AbstractCalendarActivity;
import com.android.calendar.AsyncQueryService;
import com.android.calendar.CalendarEventModel;
import com.android.calendar.CalendarEventModel.Attendee;
import com.android.calendar.R;
import com.android.calendar.Utils;
import com.android.common.Rfc822Validator;
......@@ -28,6 +29,7 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.pim.EventRecurrence;
import android.provider.Calendar.Attendees;
......@@ -40,11 +42,14 @@ import android.text.format.Time;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
import android.widget.ImageView;
import android.widget.QuickContactBadge;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.TimeZone;
public class EditEventHelper {
......@@ -133,6 +138,15 @@ public class EditEventHelper {
// if an uri is provided for an event that doesn't exist in the db.
protected boolean mEventOk = true;
public static final int ATTENDEE_NO_RESPONSE = -1;
public static final int ATTENDEE_ID_NONE = -1;
public static final int[] ATTENDEE_VALUES = {
ATTENDEE_NO_RESPONSE,
Attendees.ATTENDEE_STATUS_ACCEPTED,
Attendees.ATTENDEE_STATUS_TENTATIVE,
Attendees.ATTENDEE_STATUS_DECLINED,
};
/**
* This is the symbolic name for the key used to pass in the boolean for
* creating all-day events that is part of the extra data of the intent.
......@@ -156,15 +170,36 @@ public class EditEventHelper {
+ Calendars.CONTRIBUTOR_ACCESS + " AND " + Calendars.SELECTED + "=1";
static final String[] ATTENDEES_PROJECTION = new String[] {
Attendees.ATTENDEE_NAME, // 0
Attendees.ATTENDEE_EMAIL, // 1
Attendees.ATTENDEE_RELATIONSHIP, // 2
Attendees._ID, // 0
Attendees.ATTENDEE_NAME, // 1
Attendees.ATTENDEE_EMAIL, // 2
Attendees.ATTENDEE_RELATIONSHIP, // 3
Attendees.ATTENDEE_STATUS, // 4
};
static final int ATTENDEES_INDEX_NAME = 0;
static final int ATTENDEES_INDEX_EMAIL = 1;
static final int ATTENDEES_INDEX_RELATIONSHIP = 2;
static final int ATTENDEES_INDEX_ID = 0;
static final int ATTENDEES_INDEX_NAME = 1;
static final int ATTENDEES_INDEX_EMAIL = 2;
static final int ATTENDEES_INDEX_RELATIONSHIP = 3;
static final int ATTENDEES_INDEX_STATUS = 4;
static final String ATTENDEES_WHERE_NOT_ORGANIZER = Attendees.EVENT_ID + "=? AND "
+ Attendees.ATTENDEE_RELATIONSHIP + "<>" + Attendees.RELATIONSHIP_ORGANIZER;
static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
public static class ContactViewHolder {
QuickContactBadge badge;
ImageView presence;
int updateCounts;
}
public static class AttendeeItem {
public boolean mRemoved;
public boolean mDivider;
public String mDividerLabel;
public Attendee mAttendee;
public Drawable mBadge;
public int mPresence;
public int mUpdateCounts;
}
public EditEventHelper(AbstractCalendarActivity activity, CalendarEventModel model) {
mActivity = activity;
......@@ -364,21 +399,34 @@ public class EditEventHelper {
values.put(Attendees.ATTENDEE_EMAIL, ownerEmail);
values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER);
values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED);
values.put(Attendees.ATTENDEE_STATUS, model.mSelfAttendeeStatus);
b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI).withValues(values);
b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex);
b.withValueBackReference(Attendees.EVENT_ID, eventIdIndex);
ops.add(b.build());
}
} else if (hasAttendeeData &&
model.mSelfAttendeeStatus != originalModel.mSelfAttendeeStatus &&
model.mOwnerAttendeeId != -1) {
if (DEBUG) {
Log.d(TAG, "Setting attendee status to " + model.mSelfAttendeeStatus);
}
Uri attUri = ContentUris.withAppendedId(Attendees.CONTENT_URI, model.mOwnerAttendeeId);
values.clear();
values.put(Attendees.ATTENDEE_STATUS, model.mSelfAttendeeStatus);
values.put(Attendees.EVENT_ID, model.mId);
b = ContentProviderOperation.newUpdate(attUri).withValues(values);
ops.add(b.build());
}
// TODO: is this the right test? this currently checks if this is
// a new event or an existing event. or is this a paranoia check?
if (hasAttendeeData && (newEvent || uri != null)) {
String attendees = model.mAttendees;
String attendees = model.getAttendeesString();
String originalAttendeesString;
if (originalModel != null) {
originalAttendeesString = originalModel.mAttendees;
originalAttendeesString = originalModel.getAttendeesString();
} else {
originalAttendeesString = "";
}
......@@ -389,7 +437,8 @@ public class EditEventHelper {
// need to be deleted. use a linked hash set, so we maintain
// order (but also remove duplicates).
setDomainFromModel(model);
LinkedHashSet<Rfc822Token> newAttendees = getAddressesFromList(attendees);
HashMap<String, Attendee> newAttendees = model.mAttendeesList;
LinkedList<String> removedAttendees = new LinkedList<String>();
// the eventId is only used if eventIdIndex is -1.
// TODO: clean up this code.
......@@ -399,16 +448,15 @@ public class EditEventHelper {
// new events (being inserted into the Events table) won't
// have any existing attendees.
if (!newEvent) {
HashSet<Rfc822Token> removedAttendees = new HashSet<Rfc822Token>();
HashSet<Rfc822Token> originalAttendees = new HashSet<Rfc822Token>();
Rfc822Tokenizer.tokenize(originalAttendeesString, originalAttendees);
for (Rfc822Token originalAttendee : originalAttendees) {
if (newAttendees.contains(originalAttendee)) {
removedAttendees.clear();
HashMap<String, Attendee> originalAttendees = originalModel.mAttendeesList;
for (String originalEmail : originalAttendees.keySet()) {
if (newAttendees.containsKey(originalEmail)) {
// existing attendee. remove from new attendees set.
newAttendees.remove(originalAttendee);
newAttendees.remove(originalEmail);
} else {
// no longer in attendees. mark as removed.
removedAttendees.add(originalAttendee);
removedAttendees.add(originalEmail);
}
}
......@@ -420,12 +468,12 @@ public class EditEventHelper {
args[0] = Long.toString(eventId);
int i = 1;
StringBuilder deleteWhere = new StringBuilder(ATTENDEES_DELETE_PREFIX);
for (Rfc822Token removedAttendee : removedAttendees) {
for (String removedAttendee : removedAttendees) {
if (i > 1) {
deleteWhere.append(",");
}
deleteWhere.append("?");
args[i++] = removedAttendee.getAddress();
args[i++] = removedAttendee;
}
deleteWhere.append(")");
b.withSelection(deleteWhere.toString(), args);
......@@ -435,10 +483,10 @@ public class EditEventHelper {
if (newAttendees.size() > 0) {
// Insert the new attendees
for (Rfc822Token attendee : newAttendees) {
for (Attendee attendee : newAttendees.values()) {
values.clear();
values.put(Attendees.ATTENDEE_NAME, attendee.getName());
values.put(Attendees.ATTENDEE_EMAIL, attendee.getAddress());
values.put(Attendees.ATTENDEE_NAME, attendee.mName);
values.put(Attendees.ATTENDEE_EMAIL, attendee.mEmail);
values.put(Attendees.ATTENDEE_RELATIONSHIP,
Attendees.RELATIONSHIP_ATTENDEE);
values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
......@@ -466,17 +514,21 @@ public class EditEventHelper {
return true;
}
public LinkedHashSet<Rfc822Token> getAddressesFromList(String list) {
public static LinkedHashSet<Rfc822Token> getAddressesFromList(String list,
Rfc822Validator validator) {
LinkedHashSet<Rfc822Token> addresses = new LinkedHashSet<Rfc822Token>();
Rfc822Tokenizer.tokenize(list, addresses);
if (validator == null) {
return addresses;
}
// validate the emails, out of paranoia. they should already be
// validated on input, but drop any invalid emails just to be safe.
Iterator<Rfc822Token> addressIterator = addresses.iterator();
while (addressIterator.hasNext()) {
Rfc822Token address = addressIterator.next();
if (!mEmailValidator.isValid(address.getAddress())) {
Log.v(TAG, "Dropping invalid attendee email address: " + address);
if (!validator.isValid(address.getAddress())) {
Log.v(TAG, "Dropping invalid attendee email address: " + address.getAddress());
addressIterator.remove();
}
}
......@@ -737,9 +789,9 @@ public class EditEventHelper {
* @param forceSave if true, then save the reminders even if they didn't change
* @return true if operations to update the database were added
*/
public boolean saveRemindersWithBackRef(ArrayList<ContentProviderOperation> ops, int eventIdIndex,
ArrayList<Integer> reminderMinutes, ArrayList<Integer> originalMinutes,
boolean forceSave) {
public boolean saveRemindersWithBackRef(ArrayList<ContentProviderOperation> ops,
int eventIdIndex, ArrayList<Integer> reminderMinutes,
ArrayList<Integer> originalMinutes, boolean forceSave) {
// If the reminders have not changed, then don't update the database
if (reminderMinutes.equals(originalMinutes) && !forceSave) {
return false;
......
......@@ -17,6 +17,7 @@
package com.android.calendar.event;
import com.android.calendar.CalendarEventModel;
import com.android.calendar.CalendarEventModel.Attendee;
import com.android.calendar.CalendarPreferenceActivity;
import com.android.calendar.EmailAddressAdapter;
import com.android.calendar.R;
......@@ -47,7 +48,6 @@ import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
......@@ -57,6 +57,7 @@ import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.MultiAutoCompleteTextView;
import android.widget.ResourceCursorAdapter;
import android.widget.ScrollView;
......@@ -67,6 +68,7 @@ import android.widget.TimePicker;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.TimeZone;
public class EditEventView implements View.OnClickListener, DialogInterface.OnCancelListener,
......@@ -76,6 +78,8 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa
private static final int REMINDER_FLING_VELOCITY = 2000;
private LayoutInflater mLayoutInflater;
TextView mLoadingMessage;
ScrollView mScrollView;
Button mStartDateButton;
......@@ -91,6 +95,7 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa
Spinner mRepeatsSpinner;
Spinner mTransparencySpinner;
Spinner mVisibilitySpinner;
Spinner mResponseSpinner;
TextView mTitleTextView;
TextView mLocationTextView;
TextView mDescriptionTextView;
......@@ -98,8 +103,10 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa
TextView mTimezoneFooterView;
View mRemindersSeparator;
LinearLayout mRemindersContainer;
LinearLayout mExtraOptions;
MultiAutoCompleteTextView mAttendeesList;
ImageButton mAddAttendeesButton;
ListView mGuestList;
AttendeesAdapter mAttendeesAdapter;
private ProgressDialog mLoadingCalendarsDialog;
private AlertDialog mNoCalendarsDialog;
......@@ -181,6 +188,15 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa
}
}
private class AddAttendeeClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
mAttendeesList.performValidation();
mAttendeesAdapter.addAttendees(mAttendeesList.getText().toString());
mAttendeesList.setText("");
}
}
private class TimeClickListener implements View.OnClickListener {
private Time mTime;
......@@ -597,8 +613,24 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa
mModel.mAllDay = mAllDayCheckBox.isChecked();
mModel.mLocation = mLocationTextView.getText().toString().trim();
mModel.mDescription = mDescriptionTextView.getText().toString().trim();
if (mAttendeesList != null) {
mModel.mAttendees = mAttendeesList.getText().toString().trim();
int position = mResponseSpinner.getSelectedItemPosition();
if (position > 0) {
mModel.mSelfAttendeeStatus = EditEventHelper.ATTENDEE_VALUES[position];
}
if (mGuestList != null) {
AttendeesAdapter adapter = (AttendeesAdapter) mGuestList.getAdapter();
if (adapter != null && !adapter.isEmpty()) {
int size = adapter.getCount();
mModel.mAttendeesList.clear();
for (int i = 0; i < size; i++) {
Attendee attendee = adapter.getItem(i);
if (attendee == null || adapter.isRemoved(i)) {
continue;
}
mModel.addAttendee(attendee);
}
}
}
// If this was a new event we need to fill in the Calendar information
......@@ -652,7 +684,7 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa
if (mModification == EditEventHelper.MODIFY_SELECTED) {
selection = EditEventHelper.DOES_NOT_REPEAT;
} else {
int position = mRepeatsSpinner.getSelectedItemPosition();
position = mRepeatsSpinner.getSelectedItemPosition();
selection = mRecurrenceIndexes.get(position);
}
......@@ -672,14 +704,14 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa
mLoadingMessage = (TextView) view.findViewById(R.id.loading_message);
mScrollView = (ScrollView) view.findViewById(R.id.scroll_view);
LayoutInflater inflater = activity.getLayoutInflater();
mLayoutInflater = activity.getLayoutInflater();
// cache all the widgets
mTitleTextView = (TextView) view.findViewById(R.id.title);
mLocationTextView = (TextView) view.findViewById(R.id.location);
mDescriptionTextView = (TextView) view.findViewById(R.id.description);
mTimezoneTextView = (TextView) view.findViewById(R.id.timezone_label);
mTimezoneFooterView = (TextView) inflater.inflate(R.layout.timezone_footer, null);
mTimezoneFooterView = (TextView) mLayoutInflater.inflate(R.layout.timezone_footer, null);
mStartDateButton = (Button) view.findViewById(R.id.start_date);
mEndDateButton = (Button) view.findViewById(R.id.end_date);
mStartTimeButton = (Button) view.findViewById(R.id.start_time);
......@@ -690,21 +722,26 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa
mRepeatsSpinner = (Spinner) view.findViewById(R.id.repeats);
mTransparencySpinner = (Spinner) view.findViewById(R.id.availability);
mVisibilitySpinner = (Spinner) view.findViewById(R.id.visibility);
mResponseSpinner = (Spinner) view.findViewById(R.id.response_value);
mRemindersSeparator = view.findViewById(R.id.reminders_separator);
mRemindersContainer = (LinearLayout) view.findViewById(R.id.reminder_items_container);
mExtraOptions = (LinearLayout) view.findViewById(R.id.extra_options_container);
mSaveButton = (Button) mView.findViewById(R.id.save);
mDeleteButton = (Button) mView.findViewById(R.id.delete);
mSaveButton = (Button) view.findViewById(R.id.save);
mDeleteButton = (Button) view.findViewById(R.id.delete);
mDiscardButton = (Button) mView.findViewById(R.id.discard);
mDiscardButton = (Button) view.findViewById(R.id.discard);
mDiscardButton.setOnClickListener(this);
mAddAttendeesButton = (ImageButton) view.findViewById(R.id.attendee_add);
mAddAttendeesButton.setOnClickListener(new AddAttendeeClickListener());
mStartTime = new Time();
mEndTime = new Time();
mTimezone = TimeZone.getDefault().getID();
mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone);
mGuestList = (ListView) mView.findViewById(R.id.attendee_list);
// Display loading screen
setModel(null);
}
......@@ -863,16 +900,12 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa
ImageButton reminderRemoveButton = (ImageButton) mView.findViewById(R.id.reminder_add);
reminderRemoveButton.setOnClickListener(addReminderOnClickListener);
String attendees = model.mAttendees;
if (model.mHasAttendeeData && !TextUtils.isEmpty(attendees)) {
mAttendeesList.setText(attendees);
}
mTitleTextView.setText(model.mTitle);
mLocationTextView.setText(model.mLocation);
mDescriptionTextView.setText(model.mDescription);
mTransparencySpinner.setSelection(model.mTransparency ? 1 : 0);
mVisibilitySpinner.setSelection(model.mVisibility);
mResponseSpinner.setSelection(findResponseIndexFor(model.mSelfAttendeeStatus));
if (model.mUri != null) {
// This is an existing event so hide the calendar spinner
......@@ -886,10 +919,21 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa
populateWhen();
populateTimezone();
populateRepeats();
updateAttendees(model.mAttendeesList);
mScrollView.setVisibility(View.VISIBLE);
mLoadingMessage.setVisibility(View.GONE);
}
private int findResponseIndexFor(int response) {
int size = EditEventHelper.ATTENDEE_VALUES.length;
for (int index = 0; index < size; index++) {
if (EditEventHelper.ATTENDEE_VALUES[index] == response) {
return index;
}
}
return 0;
}
public void setCalendarsCursor(Cursor cursor) {
// If there are no syncable calendars, then we cannot allow
// creating a new event.
......@@ -983,6 +1027,15 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa
return 0;
}
public void updateAttendees(HashMap<String, Attendee> attendeesList) {
if (mAttendeesAdapter == null) {
mAttendeesAdapter = new AttendeesAdapter(mActivity, mEmailValidator);
}
if (attendeesList.size() > 0) {
mAttendeesAdapter.addAttendees(attendeesList);
mGuestList.setAdapter(mAttendeesAdapter);
}
}
private void updateRemindersVisibility(int numReminders) {
if (numReminders == 0) {
......
......@@ -20,8 +20,6 @@ import com.android.calendar.AbstractCalendarActivity;
import com.android.calendar.AsyncQueryService;
import com.android.calendar.CalendarEventModel;
import com.android.calendar.R;
import com.android.calendar.R.plurals;
import com.android.calendar.event.EditEventHelper;
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
......@@ -363,7 +361,7 @@ public class EditEventHelperTest extends AndroidTestCase {
mHelper = new EditEventHelper(mActivity, null);
mModel1 = buildTestModel();
mModel1.mAttendees = TEST_ADDRESSES2;
// mModel1.mAttendees = TEST_ADDRESSES2;
mCurrentSaveTest = SAVE_EVENT_NEW_EVENT;
assertTrue(mHelper.saveEvent(mModel1, null, 0));
......@@ -401,7 +399,7 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1 = buildTestModel();
mModel2 = buildTestModel();
mModel1.mAttendees = TEST_ADDRESSES2;
// mModel1.mAttendees = TEST_ADDRESSES2;
// Updating a recurring event with a new attendee list
mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
......@@ -409,8 +407,8 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1.mOriginalStart = TEST_START;
// The original model is assumed correct so drop the no good bit
mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
"one.two.three@email.grue";
// mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
// "one.two.three@email.grue";
mCurrentSaveTest = SAVE_EVENT_MOD_RECUR;
assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL));
......@@ -450,7 +448,7 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1 = buildTestModel();
mModel2 = buildTestModel();
mModel1.mAttendees = TEST_ADDRESSES2;
// mModel1.mAttendees = TEST_ADDRESSES2;
// Updating a recurring event with a new attendee list
mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
......@@ -458,8 +456,8 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1.mOriginalStart = TEST_START;
// The original model is assumed correct so drop the no good bit
mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
"one.two.three@email.grue";
// mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
// "one.two.three@email.grue";
// Replace an existing recurring event with a non-recurring event
mModel1.mRrule = null;
......@@ -506,7 +504,7 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1 = buildTestModel();
mModel2 = buildTestModel();
mModel1.mAttendees = TEST_ADDRESSES2;
// mModel1.mAttendees = TEST_ADDRESSES2;
// Updating a recurring event with a new attendee list
mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
......@@ -514,8 +512,8 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1.mOriginalStart = TEST_START;
// The original model is assumed correct so drop the no good bit
mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
"one.two.three@email.grue";
// mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
// "one.two.three@email.grue";
mModel2.mRrule = null;
mModel2.mEnd = TEST_END;
......@@ -540,7 +538,7 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1 = buildTestModel();
mModel2 = buildTestModel();
mModel1.mAttendees = TEST_ADDRESSES2;
// mModel1.mAttendees = TEST_ADDRESSES2;
// Updating a recurring event with a new attendee list
mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
......@@ -548,8 +546,8 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1.mOriginalStart = TEST_START;
// The original model is assumed correct so drop the no good bit
mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
"one.two.three@email.grue";
// mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
// "one.two.three@email.grue";
mModel2.mRrule = null;
mModel2.mEnd = TEST_END2;
......@@ -591,15 +589,15 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1 = buildTestModel();
mModel2 = buildTestModel();
mModel1.mAttendees = TEST_ADDRESSES2;
// mModel1.mAttendees = TEST_ADDRESSES2;
mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
// And a new start time to ensure the time fields aren't removed
mModel1.mOriginalStart = TEST_START;
// The original model is assumed correct so drop the no good bit
mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
"one.two.three@email.grue";
// mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
// "one.two.three@email.grue";
// Modify the second instance of the event
long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
......@@ -653,14 +651,14 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1 = buildTestModel();
mModel2 = buildTestModel();
mModel1.mAttendees = TEST_ADDRESSES2;
// mModel1.mAttendees = TEST_ADDRESSES2;
mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
mModel2.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
// The original model is assumed correct so drop the no good bit
mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
"one.two.three@email.grue";
// mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
// "one.two.three@email.grue";
// Modify the second instance of the event
long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
......@@ -710,7 +708,7 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1 = buildTestModel();
mModel2 = buildTestModel();
mModel1.mAttendees = TEST_ADDRESSES2;
// mModel1.mAttendees = TEST_ADDRESSES2;
mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
mModel2.mUri = mModel1.mUri;
......@@ -718,8 +716,8 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1.mOriginalStart = TEST_START;
// The original model is assumed correct so drop the no good bit
mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
"one.two.three@email.grue";
// mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
// "one.two.three@email.grue";
// Move the event one day but keep original start set to the first instance
long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
......@@ -769,7 +767,7 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1 = buildTestModel();
mModel2 = buildTestModel();
mModel1.mAttendees = TEST_ADDRESSES2;
// mModel1.mAttendees = TEST_ADDRESSES2;
mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
mModel2.mUri = mModel1.mUri;
......@@ -777,8 +775,8 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1.mOriginalStart = TEST_START;
// The original model is assumed correct so drop the no good bit
mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
"one.two.three@email.grue";
// mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
// "one.two.three@email.grue";
// Move the event one day but keep original start set to the first instance
long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
......@@ -828,14 +826,14 @@ public class EditEventHelperTest extends AndroidTestCase {
mModel1 = buildTestModel();
mModel2 = buildTestModel();
mModel1.mAttendees = TEST_ADDRESSES2;
// mModel1.mAttendees = TEST_ADDRESSES2;
mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
mModel2.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
// The original model is assumed correct so drop the no good bit
mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
"one.two.three@email.grue";
// mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
// "one.two.three@email.grue";
// Move the event one day and the original start so it references the second instance
long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
......@@ -887,7 +885,7 @@ public class EditEventHelperTest extends AndroidTestCase {
expected.add(new Rfc822Token("First Last", "first@email.com", "comment"));
expected.add(new Rfc822Token(null, "one.two.three@email.grue", ""));
LinkedHashSet<Rfc822Token> actual = mHelper.getAddressesFromList(TEST_ADDRESSES);
LinkedHashSet<Rfc822Token> actual = mHelper.getAddressesFromList(TEST_ADDRESSES, null);
assertEquals(actual, expected);
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment