Improving display of birthdays

Change-Id: I1a098c52e2b6c8968ce811b795611ab028609658
diff --git a/src/com/android/contacts/model/ExchangeSource.java b/src/com/android/contacts/model/ExchangeSource.java
index 9fb5f5b..6f91840 100644
--- a/src/com/android/contacts/model/ExchangeSource.java
+++ b/src/com/android/contacts/model/ExchangeSource.java
@@ -56,6 +56,7 @@
         inflateOrganization(context, inflateLevel);
         inflatePhoto(context, inflateLevel);
         inflateNote(context, inflateLevel);
+        inflateEvent(context, inflateLevel);
         inflateWebsite(context, inflateLevel);
         inflateGroupMembership(context, inflateLevel);
 
diff --git a/src/com/android/contacts/model/FallbackSource.java b/src/com/android/contacts/model/FallbackSource.java
index 4d9b2e9..dbd56fb 100644
--- a/src/com/android/contacts/model/FallbackSource.java
+++ b/src/com/android/contacts/model/FallbackSource.java
@@ -39,8 +39,6 @@
 import android.provider.ContactsContract.CommonDataKinds.Website;
 import android.view.inputmethod.EditorInfo;
 
-import java.util.Locale;
-
 public class FallbackSource extends ContactsSource {
     protected static final int FLAGS_PHONE = EditorInfo.TYPE_CLASS_PHONE;
     protected static final int FLAGS_EMAIL = EditorInfo.TYPE_CLASS_TEXT
@@ -53,6 +51,7 @@
             | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
     protected static final int FLAGS_NOTE = EditorInfo.TYPE_CLASS_TEXT
             | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+    protected static final int FLAGS_EVENT = EditorInfo.TYPE_CLASS_TEXT;
     protected static final int FLAGS_WEBSITE = EditorInfo.TYPE_CLASS_TEXT
             | EditorInfo.TYPE_TEXT_VARIATION_URI;
     protected static final int FLAGS_POSTAL = EditorInfo.TYPE_CLASS_TEXT
@@ -109,6 +108,10 @@
         return new EditType(type, Organization.getTypeLabelResource(type));
     }
 
+    protected EditType buildEventType(int type) {
+        return new EditType(type, Event.getTypeResource(type));
+    }
+
     protected DataKind inflateStructuredName(Context context, int inflateLevel) {
         DataKind kind = getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE);
         if (kind == null) {
@@ -408,11 +411,26 @@
     protected DataKind inflateEvent(Context context, int inflateLevel) {
         DataKind kind = getKindForMimetype(Event.CONTENT_ITEM_TYPE);
         if (kind == null) {
+            // TODO make the event field editable.  The difficulty is that Google Contacts
+            // drops incorrectly formatted dates.  This means that we would
+            // need to know exactly how the sync adapter wants the date formatted and
+            // then have a rigid UI and automatic formatting of the date for the sync adapter.
             kind = addKind(new DataKind(Event.CONTENT_ITEM_TYPE,
                     R.string.eventLabelsGroup, -1, 150, false));
             kind.secondary = true;
             kind.actionHeader = new EventActionInflater();
             kind.actionBody = new SimpleInflater(Event.START_DATE);
+
+            kind.typeColumn = Event.TYPE;
+            kind.typeList = Lists.newArrayList();
+            kind.typeList.add(buildEventType(Event.TYPE_BIRTHDAY));
+            kind.typeList.add(buildEventType(Event.TYPE_ANNIVERSARY));
+            kind.typeList.add(buildEventType(Event.TYPE_OTHER));
+            kind.typeList.add(buildEventType(Event.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+                    Event.LABEL));
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Event.DATA, R.string.eventLabelsGroup, FLAGS_EVENT));
         }
 
         return kind;
diff --git a/src/com/android/contacts/model/GoogleSource.java b/src/com/android/contacts/model/GoogleSource.java
index fc290c6..f8abb94 100644
--- a/src/com/android/contacts/model/GoogleSource.java
+++ b/src/com/android/contacts/model/GoogleSource.java
@@ -22,14 +22,10 @@
 import android.content.Context;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.Groups;
 
 public class GoogleSource extends FallbackSource {
     public static final String ACCOUNT_TYPE = "com.google";
 
-    private static final String SELECTION_GROUPS_BY_TITLE_AND_ACCOUNT =
-            Groups.TITLE + "=? AND " + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?";
-
     public GoogleSource(String resPackageName) {
         this.accountType = ACCOUNT_TYPE;
         this.resPackageName = null;
diff --git a/src/com/android/contacts/util/DateUtils.java b/src/com/android/contacts/util/DateUtils.java
new file mode 100644
index 0000000..39eeb84
--- /dev/null
+++ b/src/com/android/contacts/util/DateUtils.java
@@ -0,0 +1,102 @@
+/*
+ * 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.contacts.util;
+
+import android.content.Context;
+import android.text.format.DateFormat;
+
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Utility methods for processing dates.
+ */
+public class DateUtils {
+
+    private static final SimpleDateFormat NO_YEAR_DATE_FORMAT = new SimpleDateFormat("--MM-dd");
+
+    private static final SimpleDateFormat[] DATE_FORMATS = {
+        NO_YEAR_DATE_FORMAT,
+        new SimpleDateFormat("yyyy-MM-dd"),
+        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"),
+    };
+    static {
+        for (SimpleDateFormat format : DATE_FORMATS) {
+            format.setLenient(true);
+        }
+    }
+
+    private static final java.text.DateFormat FORMAT_WITHOUT_YEAR_MONTH_FIRST =
+            new SimpleDateFormat("MMMM dd");
+
+    private static final java.text.DateFormat FORMAT_WITHOUT_YEAR_DATE_FIRST =
+            new SimpleDateFormat("dd MMMM");
+
+    /**
+     * Parses the supplied string to see if it looks like a date. If so,
+     * returns the same date in a cleaned-up format.  Otherwise, returns
+     * the supplied string unchanged.
+     */
+    public static String formatDate(Context context, String string) {
+        if (string == null) {
+            return null;
+        }
+
+        string = string.trim();
+        if (string.length() == 0) {
+            return string;
+        }
+
+        ParsePosition parsePosition = new ParsePosition(0);
+
+        for (int i = 0; i < DATE_FORMATS.length; i++) {
+            SimpleDateFormat f = DATE_FORMATS[i];
+            synchronized (f) {
+                parsePosition.setIndex(0);
+                Date date = f.parse(string, parsePosition);
+                if (parsePosition.getIndex() == string.length()) {
+                    java.text.DateFormat outFormat;
+                    if (f == NO_YEAR_DATE_FORMAT) {
+                        outFormat = isMonthBeforeDate(context) ? FORMAT_WITHOUT_YEAR_MONTH_FIRST
+                                : FORMAT_WITHOUT_YEAR_DATE_FIRST;
+                    } else {
+                        outFormat = DateFormat.getDateFormat(context);
+                    }
+
+                    synchronized (outFormat) {
+                        return outFormat.format(date);
+                    }
+                }
+            }
+        }
+        return string;
+    }
+
+    private static boolean isMonthBeforeDate(Context context) {
+        char[] dateFormatOrder = DateFormat.getDateFormatOrder(context);
+        for (int i = 0; i < dateFormatOrder.length; i++) {
+            if (dateFormatOrder[i] == DateFormat.DATE) {
+                return false;
+            }
+            if (dateFormatOrder[i] == DateFormat.MONTH) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/contacts/views/detail/ContactDetailFragment.java b/src/com/android/contacts/views/detail/ContactDetailFragment.java
index 73756fd..1292ad5 100644
--- a/src/com/android/contacts/views/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/views/detail/ContactDetailFragment.java
@@ -30,6 +30,7 @@
 import com.android.contacts.model.Sources;
 import com.android.contacts.util.Constants;
 import com.android.contacts.util.DataStatus;
+import com.android.contacts.util.DateUtils;
 import com.android.contacts.util.PhoneCapabilityTester;
 import com.android.contacts.views.ContactLoader;
 import com.android.contacts.views.GroupMetaData;
@@ -59,6 +60,7 @@
 import android.os.ServiceManager;
 import android.provider.ContactsContract.CommonDataKinds;
 import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
 import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.CommonDataKinds.Nickname;
@@ -102,6 +104,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 
 public class ContactDetailFragment extends Fragment implements OnCreateContextMenuListener,
         OnItemClickListener, SelectAccountDialogFragment.Listener {
@@ -495,6 +498,10 @@
                     // (Then, we'd also update FallbackSource.java to set
                     // secondary=false for this field, and tweak the weight
                     // of its DataKind.)
+                } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                    entry.data = DateUtils.formatDate(mContext, entry.data);
+                    entry.uri = null;
+                    mOtherEntries.add(entry);
                 } else {
                     // Handle showing custom rows
                     entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);