Merge "Made QuickContacts on tablet a little smaller"
diff --git a/res/layout/date_picker.xml b/res/layout/date_picker.xml
index 4fb2318..4be95c0 100644
--- a/res/layout/date_picker.xml
+++ b/res/layout/date_picker.xml
@@ -19,12 +19,13 @@
 
 <!-- Layout of date picker-->
 
-<!-- Warning: everything within the parent is removed and re-ordered depending
-     on the date format selected by the user. -->
+<!-- The width of this container is manually set a little bigger than the one of the children
+     contained in it. This helps to prevent rounding errors when toggling the "Show year" option -->
+
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
     android:layout_gravity="center_horizontal"
-    android:layout_width="wrap_content"
+    android:layout_width="270dip"
     android:layout_height="wrap_content">
 
     <CheckBox
@@ -33,8 +34,11 @@
         android:paddingTop="5dip"
         android:paddingBottom="5dip"
         android:textAppearance="?android:attr/textAppearanceLarge"
+        android:layout_gravity="center_horizontal"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"/>
+    <!-- Warning: everything within the parent is removed and re-ordered depending
+         on the date format selected by the user. -->
     <LinearLayout
         android:id="@+id/parent"
         android:orientation="horizontal"
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index ec797e7..830fc00 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -220,7 +220,7 @@
     <string name="searching_vcard_message" product="default" msgid="3962269894118092049">"Menelusuri data vCard pada kartu SD..."</string>
     <string name="scanning_sdcard_failed_message" product="nosdcard" msgid="7221682312959229201">"Penyimpanan tidak dapat dipindai. (Alasan: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
     <string name="scanning_sdcard_failed_message" product="default" msgid="189023067829510792">"Kartu SD tidak dapat dipindai. (Alasan: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
-    <string name="fail_reason_io_error" msgid="6748358842976073255">"Galat I/O"</string>
+    <string name="fail_reason_io_error" msgid="6748358842976073255">"Kesalahan I/O"</string>
     <string name="fail_reason_low_memory_during_import" msgid="875222757734882898">"Memori tidak cukup. File mungkin terlalu besar."</string>
     <string name="fail_reason_vcard_parse_error" msgid="888263542360355784">"Tidak dapat mengurai vCard karena alasan yang tak terduga."</string>
     <string name="fail_reason_not_supported" msgid="8219562769267148825">"Format tidak didukung."</string>
@@ -229,7 +229,7 @@
     <string name="import_failure_no_vcard_file" product="default" msgid="1754014167874286173">"Tidak ditemukan file vCard pada kartu SD."</string>
     <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="6427931733267328564">"Tidak dapat mengumpulkan informasi meta dari file vCard yang diberikan."</string>
     <string name="fail_reason_failed_to_read_files" msgid="5823434810622484922">"Satu file atau lebih tidak dapat diimpor (%s)."</string>
-    <string name="fail_reason_unknown" msgid="1714092345030570863">"Galat tidak dikenal."</string>
+    <string name="fail_reason_unknown" msgid="1714092345030570863">"Kesalahan tidak dikenal."</string>
     <string name="select_vcard_title" msgid="7791371083694672861">"Pilih file vCard"</string>
     <string name="caching_vcard_title" msgid="1226272312940516605">"Menyimpan ke tembolok"</string>
     <string name="caching_vcard_message" msgid="4926308675041506756">"Menyimpan vCard ke tembolok untuk penyimpanan lokal sementara. Impor yang sebenarnya akan segera dimulai."</string>
@@ -259,7 +259,7 @@
     <string name="exporting_contact_list_title" msgid="9072240631534457415">"Mengekspor data kenalan"</string>
     <string name="exporting_contact_list_message" msgid="7181663157672374569">"Data kenalan Anda sedang diekspor ke: <xliff:g id="FILE_NAME">%s</xliff:g>."</string>
     <string name="fail_reason_could_not_initialize_exporter" msgid="707260459259688510">"Tidak dapat memulai pengeskpor: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\"."</string>
-    <string name="fail_reason_error_occurred_during_export" msgid="3018855323913649063">"Terjadi galat saat ekspor: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\"."</string>
+    <string name="fail_reason_error_occurred_during_export" msgid="3018855323913649063">"Terjadi kesalahan saat ekspor: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\"."</string>
     <string name="composer_failed_to_get_database_infomation" msgid="1765944280846236723">"Tidak dapat memperoleh informasi basis data."</string>
     <string name="composer_has_no_exportable_contact" product="tablet" msgid="6991449891825077743">"Tidak ada data kenalan yang dapat diekspor. Jika Anda menyimpan data kenalan di tablet, beberapa penyedia data mungkin tidak mengizinkan data kenalan untuk diekspor dari tablet."</string>
     <string name="composer_has_no_exportable_contact" product="default" msgid="3296493229040294335">"Tidak ada data kenalan yang dapat diekspor. Jika Anda menyimpan data kenalan pada ponsel, beberapa penyedia data tidak mengizinkan data kenalan diekspor dari ponsel."</string>
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 6de361d..3fbca54 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -21,6 +21,7 @@
 import com.android.contacts.model.EntityDelta;
 import com.android.contacts.model.EntityDeltaList;
 import com.android.contacts.model.EntityModifier;
+import com.android.contacts.util.CallerInfoCacheUtils;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Sets;
 
@@ -177,11 +178,15 @@
 
     @Override
     protected void onHandleIntent(Intent intent) {
+        // Call an appropriate method. If we're sure it affects how incoming phone calls are
+        // handled, then notify the fact to in-call screen.
         String action = intent.getAction();
         if (ACTION_NEW_RAW_CONTACT.equals(action)) {
             createRawContact(intent);
+            CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
         } else if (ACTION_SAVE_CONTACT.equals(action)) {
             saveContact(intent);
+            CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
         } else if (ACTION_CREATE_GROUP.equals(action)) {
             createGroup(intent);
         } else if (ACTION_RENAME_GROUP.equals(action)) {
@@ -198,12 +203,16 @@
             clearPrimary(intent);
         } else if (ACTION_DELETE_CONTACT.equals(action)) {
             deleteContact(intent);
+            CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
         } else if (ACTION_JOIN_CONTACTS.equals(action)) {
             joinContacts(intent);
+            CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
         } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) {
             setSendToVoicemail(intent);
+            CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
         } else if (ACTION_SET_RINGTONE.equals(action)) {
             setRingtone(intent);
+            CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
         }
     }
 
diff --git a/src/com/android/contacts/datepicker/DatePicker.java b/src/com/android/contacts/datepicker/DatePicker.java
index 7ea9641..268243d 100644
--- a/src/com/android/contacts/datepicker/DatePicker.java
+++ b/src/com/android/contacts/datepicker/DatePicker.java
@@ -108,6 +108,7 @@
         mDayPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
         mDayPicker.setOnLongPressUpdateInterval(100);
         mDayPicker.setOnValueChangedListener(new OnValueChangeListener() {
+            @Override
             public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
                 mDay = newVal;
                 notifyDateChanged();
@@ -137,6 +138,7 @@
 
         mMonthPicker.setOnLongPressUpdateInterval(200);
         mMonthPicker.setOnValueChangedListener(new OnValueChangeListener() {
+            @Override
             public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
 
                 /* We display the month 1-12 but store it 0-11 so always
@@ -152,6 +154,7 @@
         mYearPicker = (NumberPicker) findViewById(R.id.year);
         mYearPicker.setOnLongPressUpdateInterval(100);
         mYearPicker.setOnValueChangedListener(new OnValueChangeListener() {
+            @Override
             public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
                 mYear = newVal;
                 // Adjust max day for leap years if needed
@@ -353,10 +356,12 @@
         public static final Parcelable.Creator<SavedState> CREATOR =
                 new Creator<SavedState>() {
 
+                    @Override
                     public SavedState createFromParcel(Parcel in) {
                         return new SavedState(in);
                     }
 
+                    @Override
                     public SavedState[] newArray(int size) {
                         return new SavedState[size];
                     }
diff --git a/src/com/android/contacts/datepicker/DatePickerDialog.java b/src/com/android/contacts/datepicker/DatePickerDialog.java
index 112b96e..b0c4ed6 100644
--- a/src/com/android/contacts/datepicker/DatePickerDialog.java
+++ b/src/com/android/contacts/datepicker/DatePickerDialog.java
@@ -19,6 +19,10 @@
 // This is a fork of the standard Android DatePicker that additionally allows toggling the year
 // on/off. It uses some private API so that not everything has to be copied.
 
+import com.android.contacts.R;
+import com.android.contacts.datepicker.DatePicker.OnDateChangedListener;
+import com.android.contacts.util.DateUtils;
+
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -30,10 +34,8 @@
 import android.view.View;
 import android.widget.TextView;
 
-import com.android.contacts.R;
-import com.android.contacts.datepicker.DatePicker.OnDateChangedListener;
-
-import java.text.DateFormatSymbols;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
 import java.util.Calendar;
 
 /**
@@ -53,8 +55,8 @@
     private final DatePicker mDatePicker;
     private final OnDateSetListener mCallBack;
     private final Calendar mCalendar;
-    private final java.text.DateFormat mTitleDateFormat;
-    private final String[] mWeekDays;
+    private final DateFormat mTitleDateFormat;
+    private final DateFormat mTitleNoYearDateFormat;
 
     private int mInitialYear;
     private int mInitialMonth;
@@ -148,11 +150,10 @@
         mInitialYear = year;
         mInitialMonth = monthOfYear;
         mInitialDay = dayOfMonth;
-        DateFormatSymbols symbols = new DateFormatSymbols();
-        mWeekDays = symbols.getShortWeekdays();
 
-        mTitleDateFormat = java.text.DateFormat.
-                                getDateInstance(java.text.DateFormat.FULL);
+        mTitleDateFormat = DateFormat.getDateInstance(DateFormat.FULL);
+        mTitleNoYearDateFormat = new SimpleDateFormat(
+                DateUtils.isMonthBeforeDay(getContext()) ? "MMMM dd" : "dd MMMM");
         mCalendar = Calendar.getInstance();
         updateTitle(mInitialYear, mInitialMonth, mInitialDay);
 
@@ -182,6 +183,7 @@
         title.setEllipsize(TruncateAt.END);
     }
 
+    @Override
     public void onClick(DialogInterface dialog, int which) {
         if (mCallBack != null) {
             mDatePicker.clearFocus();
@@ -190,8 +192,8 @@
         }
     }
 
-    public void onDateChanged(DatePicker view, int year,
-            int month, int day) {
+    @Override
+    public void onDateChanged(DatePicker view, int year, int month, int day) {
         updateTitle(year, month, day);
     }
 
@@ -206,7 +208,9 @@
         mCalendar.set(Calendar.YEAR, year);
         mCalendar.set(Calendar.MONTH, month);
         mCalendar.set(Calendar.DAY_OF_MONTH, day);
-        setTitle(mTitleDateFormat.format(mCalendar.getTime()));
+        final DateFormat dateFormat =
+                year == 0 ? mTitleNoYearDateFormat : mTitleDateFormat;
+        setTitle(dateFormat.format(mCalendar.getTime()));
     }
 
     @Override
diff --git a/src/com/android/contacts/editor/EventFieldEditorView.java b/src/com/android/contacts/editor/EventFieldEditorView.java
index 08cbaef..538d4dc 100644
--- a/src/com/android/contacts/editor/EventFieldEditorView.java
+++ b/src/com/android/contacts/editor/EventFieldEditorView.java
@@ -29,7 +29,6 @@
 
 import android.app.Dialog;
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.text.TextUtils;
@@ -238,10 +237,11 @@
                 final Calendar outCalendar =
                         Calendar.getInstance(DateUtils.UTC_TIMEZONE, Locale.US);
 
-                // If no year specified, set it to 1900. The format string will ignore that year
+                // If no year specified, set it to 2000 (we could pick any leap year here).
+                // The format string will ignore that year.
                 // For formats other than Exchange, the time of the day is ignored
                 outCalendar.clear();
-                outCalendar.set(year == 0 ? 1900 : year, monthOfYear, dayOfMonth,
+                outCalendar.set(year == 0 ? 2000 : year, monthOfYear, dayOfMonth,
                         DEFAULT_HOUR, 0, 0);
 
                 final String resultString;
diff --git a/src/com/android/contacts/util/CallerInfoCacheUtils.java b/src/com/android/contacts/util/CallerInfoCacheUtils.java
new file mode 100644
index 0000000..9e53159
--- /dev/null
+++ b/src/com/android/contacts/util/CallerInfoCacheUtils.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 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.content.Intent;
+
+/**
+ * Utilities for managing CallerInfoCache.
+ *
+ * The cache lives in Phone package and is used as fallback storage when database lookup is slower
+ * than expected. It remembers some information necessary for responding to incoming calls
+ * (e.g. custom ringtone settings, send-to-voicemail).
+ *
+ * Even though the cache will be updated periodically, Contacts app can request the cache update
+ * via broadcast Intent. This class provides that mechanism, and possibly other misc utilities
+ * for the update mechanism.
+ */
+public final class CallerInfoCacheUtils {
+    private static final String UPDATE_CALLER_INFO_CACHE =
+            "com.android.phone.UPDATE_CALLER_INFO_CACHE";
+
+    private CallerInfoCacheUtils() {
+    }
+
+    /**
+     * Sends an Intent, notifying CallerInfo cache should be updated.
+     *
+     * Note: CallerInfo is *not* part of public API, and no guarantee is available around its
+     * specific behavior. In practice this will only be used by Phone package, but may change
+     * in the future.
+     *
+     * See also CallerInfoCache in Phone package for more information.
+     */
+    public static void sendUpdateCallerInfoCacheIntent(Context context) {
+        context.sendBroadcast(new Intent(UPDATE_CALLER_INFO_CACHE));
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/util/DateUtils.java b/src/com/android/contacts/util/DateUtils.java
index 1ea84a1..d0bb68f 100644
--- a/src/com/android/contacts/util/DateUtils.java
+++ b/src/com/android/contacts/util/DateUtils.java
@@ -56,7 +56,7 @@
     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 =
+    private static final java.text.DateFormat FORMAT_WITHOUT_YEAR_DAY_FIRST =
             new SimpleDateFormat("dd MMMM");
 
     static {
@@ -66,7 +66,7 @@
         }
         NO_YEAR_DATE_FORMAT.setTimeZone(UTC_TIMEZONE);
         FORMAT_WITHOUT_YEAR_MONTH_FIRST.setTimeZone(UTC_TIMEZONE);
-        FORMAT_WITHOUT_YEAR_DATE_FIRST.setTimeZone(UTC_TIMEZONE);
+        FORMAT_WITHOUT_YEAR_DAY_FIRST.setTimeZone(UTC_TIMEZONE);
     }
 
     /**
@@ -112,9 +112,9 @@
         }
 
         if (parsePosition.getIndex() == string.length()) {
-            java.text.DateFormat outFormat = isMonthBeforeDate(context)
+            java.text.DateFormat outFormat = isMonthBeforeDay(context)
                     ? FORMAT_WITHOUT_YEAR_MONTH_FIRST
-                    : FORMAT_WITHOUT_YEAR_DATE_FIRST;
+                    : FORMAT_WITHOUT_YEAR_DAY_FIRST;
             synchronized (outFormat) {
                 return outFormat.format(date);
             }
@@ -135,7 +135,7 @@
         return string;
     }
 
-    private static boolean isMonthBeforeDate(Context context) {
+    public static boolean isMonthBeforeDay(Context context) {
         char[] dateFormatOrder = DateFormat.getDateFormatOrder(context);
         for (int i = 0; i < dateFormatOrder.length; i++) {
             if (dateFormatOrder[i] == DateFormat.DATE) {
diff --git a/src/com/android/contacts/util/MoreMath.java b/src/com/android/contacts/util/MoreMath.java
index 6f28ccd..db76fe4 100644
--- a/src/com/android/contacts/util/MoreMath.java
+++ b/src/com/android/contacts/util/MoreMath.java
@@ -24,6 +24,16 @@
      * If the input value lies outside of the specified range, return the nearer
      * bound. Otherwise, return the input value, unchanged.
      */
+    public static int clamp(int input, int lowerBound, int upperBound) {
+        if (input < lowerBound) return lowerBound;
+        if (input > upperBound) return upperBound;
+        return input;
+    }
+
+    /**
+     * If the input value lies outside of the specified range, return the nearer
+     * bound. Otherwise, return the input value, unchanged.
+     */
     public static float clamp(float input, float lowerBound, float upperBound) {
         if (input < lowerBound) return lowerBound;
         if (input > upperBound) return upperBound;