Merge "revert the stripping of System Group: from the names of google system groups" into froyo
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 3aa5098..6fd03f7 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -3287,8 +3287,6 @@
                 foundContactsText.setText(text);
             }
 
-            mPhotoLoader.clear();
-
             super.changeCursor(cursor);
             // Update the indexer for the fast scroll widget
             updateIndexer(cursor);
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index a8b3f1e..4dcf5d5 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -31,6 +31,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
+import android.os.FileUtils;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.pim.vcard.VCardConfig;
@@ -53,9 +54,12 @@
 import android.text.style.RelativeSizeSpan;
 import android.util.Log;
 
+import java.io.BufferedOutputStream;
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -153,6 +157,7 @@
         private boolean mCanceled;
         private PowerManager.WakeLock mWakeLock;
         private Uri mUri;
+        private File mTempFile;
 
         private List<VCardFile> mSelectedVCardFileList;
         private List<String> mErrorFileNameList;
@@ -190,11 +195,18 @@
             boolean shouldCallFinish = true;
             mWakeLock.acquire();
             Uri createdUri = null;
+            mTempFile = null;
             // Some malicious vCard data may make this thread broken
             // (e.g. OutOfMemoryError).
             // Even in such cases, some should be done.
             try {
                 if (mUri != null) {  // Read one vCard expressed by mUri
+                    final Uri uri = getReopenableUri(mUri);
+                    if (uri == null) {
+                        shouldCallFinish = false;
+                        return;
+                    }
+
                     mProgressDialogForReadVCard.setProgressNumberFormat("");
                     mProgressDialogForReadVCard.setProgress(0);
 
@@ -208,16 +220,15 @@
                     VCardSourceDetector detector = new VCardSourceDetector();
                     VCardInterpreterCollection builderCollection = new VCardInterpreterCollection(
                             Arrays.asList(counter, detector));
-
                     boolean result;
                     try {
-                        result = readOneVCardFile(mUri,
+                        result = readOneVCardFile(uri,
                                 VCardConfig.DEFAULT_CHARSET, builderCollection, null, true, null);
                     } catch (VCardNestedException e) {
                         try {
                             // Assume that VCardSourceDetector was able to detect the source.
                             // Try again with the detector.
-                            result = readOneVCardFile(mUri,
+                            result = readOneVCardFile(uri,
                                     VCardConfig.DEFAULT_CHARSET, counter, detector, false, null);
                         } catch (VCardNestedException e2) {
                             result = false;
@@ -239,7 +250,7 @@
                     mProgressDialogForReadVCard.setIndeterminate(false);
                     mProgressDialogForReadVCard.setMax(counter.getCount());
                     String charset = detector.getEstimatedCharset();
-                    createdUri = doActuallyReadOneVCard(mUri, null, charset, true, detector,
+                    createdUri = doActuallyReadOneVCard(uri, null, charset, true, detector,
                             mErrorFileNameList);
                 } else {  // Read multiple files.
                     mProgressDialogForReadVCard.setProgressNumberFormat(
@@ -251,7 +262,13 @@
                         if (mCanceled) {
                             return;
                         }
-                        final Uri uri = Uri.parse("file://" + vcardFile.getCanonicalPath());
+                        // TODO: detect scheme!
+                        final Uri uri = getReopenableUri(
+                                Uri.parse("file://" + vcardFile.getCanonicalPath()));
+                        if (uri == null) {
+                            shouldCallFinish = false;
+                            return;
+                        }
 
                         VCardSourceDetector detector = new VCardSourceDetector();
                         try {
@@ -271,6 +288,12 @@
             } finally {
                 mWakeLock.release();
                 mProgressDialogForReadVCard.dismiss();
+                if (mTempFile != null) {
+                    if (!mTempFile.delete()) {
+                        Log.w(LOG_TAG, "Failed to delete a cache file.");
+                    }
+                    mTempFile = null;
+                }
                 // finish() is called via mCancelListener, which is used in DialogDisplayer.
                 if (shouldCallFinish && !isFinishing()) {
                     if (mErrorFileNameList == null || mErrorFileNameList.isEmpty()) {
@@ -310,6 +333,49 @@
             }
         }
 
+        private Uri getReopenableUri(final Uri uri) {
+            if ("file".equals(uri.getScheme())) {
+                return uri;
+            } else {
+                // We may not be able to scan a given uri more than once when it does not
+                // point to a local file, while it is necessary to scan it more than once
+                // because of vCard limitation. We rely on a local temporary file instead.
+                //
+                // e.g. Email app's AttachmentProvider is able to give us a content Uri
+                // with an attachment file, but we cannot "sometimes" (not always) scan
+                // the Uri more than once because of permission revocation.
+                InputStream is = null;
+                OutputStream os = null;
+                Uri reopenableUri = null;
+                try {
+                    is = mResolver.openInputStream(uri);
+                    File dir = getDir("tmp", MODE_PRIVATE);
+                    mTempFile = dir.createTempFile("vcf", null, dir);
+                    FileUtils.copyToFile(is, mTempFile);
+                    reopenableUri = Uri.parse("file://" + mTempFile.getCanonicalPath());
+                } catch (IOException e) {
+                    mHandler.post(new DialogDisplayer(
+                            getString(R.string.fail_reason_io_error) +
+                            ": " + e.getLocalizedMessage()));
+                    return null;
+                } finally {
+                    if (is != null) {
+                        try {
+                            is.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                    if (os != null) {
+                        try {
+                            os.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+                return reopenableUri;
+            }
+        }
+
         private Uri doActuallyReadOneVCard(Uri uri, Account account,
                 String charset, boolean showEntryParseProgress,
                 VCardSourceDetector detector, List<String> errorFileNameList) {
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index 2b2a8f7..ead6a4a 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -402,7 +402,7 @@
 
         // Contains an Id.
         final long uriContactId = Long.parseLong(segments.get(3));
-        final String uriLookupKey = segments.get(2);
+        final String uriLookupKey = Uri.encode(segments.get(2));
         final Uri dataUri = Uri.withAppendedPath(
                 ContentUris.withAppendedId(Contacts.CONTENT_URI, uriContactId),
                 Contacts.Data.CONTENT_DIRECTORY);
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index 25132fb..c70cff6 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -67,7 +67,6 @@
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.Contacts.Data;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
@@ -111,6 +110,9 @@
     private static final String KEY_EDIT_STATE = "state";
     private static final String KEY_RAW_CONTACT_ID_REQUESTING_PHOTO = "photorequester";
     private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
+    private static final String KEY_CURRENT_PHOTO_FILE = "currentphotofile";
+    private static final String KEY_QUERY_SELECTION = "queryselection";
+    private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin";
 
     /** The result code when view activity should close after edit returns */
     public static final int RESULT_CLOSE_VIEW_ACTIVITY = 777;
@@ -265,6 +267,11 @@
 
         outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
         outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
+        if (mCurrentPhotoFile != null) {
+            outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile.toString());
+        }
+        outState.putString(KEY_QUERY_SELECTION, mQuerySelection);
+        outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
         super.onSaveInstanceState(outState);
     }
 
@@ -275,6 +282,13 @@
         mRawContactIdRequestingPhoto = savedInstanceState.getLong(
                 KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
         mViewIdGenerator = savedInstanceState.getParcelable(KEY_VIEW_ID_GENERATOR);
+        String fileName = savedInstanceState.getString(KEY_CURRENT_PHOTO_FILE);
+        if (fileName != null) {
+            mCurrentPhotoFile = new File(fileName);
+        }
+        mQuerySelection = savedInstanceState.getString(KEY_QUERY_SELECTION);
+        mContactIdForJoin = savedInstanceState.getLong(KEY_CONTACT_ID_FOR_JOIN);
+
         bindEditors();
 
         super.onRestoreInstanceState(savedInstanceState);
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 593d6cc..7af1a54 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -31,6 +31,11 @@
         android:targetPackage="com.android.contacts"
         android:label="Contacts launch performance">
     </instrumentation>
-    
+
+
+    <instrumentation android:name="com.android.contacts.DialerLaunchPerformance"
+        android:targetPackage="com.android.contacts"
+        android:label="Dialer launch performance">
+    </instrumentation>
 
 </manifest> 
diff --git a/tests/src/com/android/contacts/ContactsLaunchPerformance.java b/tests/src/com/android/contacts/ContactsLaunchPerformance.java
index bd60e70..9177457 100644
--- a/tests/src/com/android/contacts/ContactsLaunchPerformance.java
+++ b/tests/src/com/android/contacts/ContactsLaunchPerformance.java
@@ -17,25 +17,22 @@
 package com.android.contacts;
 
 import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
 import android.test.LaunchPerformanceBase;
 import android.os.Bundle;
 
-import java.util.Map;
-
 /**
  * Instrumentation class for Address Book launch performance testing.
  */
 public class ContactsLaunchPerformance extends LaunchPerformanceBase {
 
-    public static final String LOG_TAG = "ContactsLaunchPerformance";
-
-    public ContactsLaunchPerformance() {
-        super();
-    }
-
     @Override
     public void onCreate(Bundle arguments) {
-        mIntent.setClassName(getTargetContext(), "com.android.contacts.ContactsListActivity");
+        mIntent.setAction(Intent.ACTION_MAIN);
+        mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        mIntent.setComponent(new ComponentName("com.android.contacts",
+                "com.android.contacts.DialtactsContactsEntryActivity"));
 
         start();
     }
diff --git a/tests/src/com/android/contacts/DialerLaunchPerformance.java b/tests/src/com/android/contacts/DialerLaunchPerformance.java
new file mode 100644
index 0000000..ae78082
--- /dev/null
+++ b/tests/src/com/android/contacts/DialerLaunchPerformance.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.test.LaunchPerformanceBase;
+
+/**
+ * Instrumentation class for Address Book launch performance testing.
+ */
+public class DialerLaunchPerformance extends LaunchPerformanceBase {
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        mIntent.setAction(Intent.ACTION_MAIN);
+        mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        mIntent.setComponent(new ComponentName("com.android.contacts",
+                "com.android.contacts.DialtactsActivity"));
+
+        start();
+    }
+
+    /**
+     * Calls LaunchApp and finish.
+     */
+    @Override
+    public void onStart() {
+        super.onStart();
+        LaunchApp();
+        finish(Activity.RESULT_OK, mResults);
+    }
+}
diff --git a/tests/src/com/android/contacts/EntityDeltaTests.java b/tests/src/com/android/contacts/EntityDeltaTests.java
index 70a506b..fa716c7 100644
--- a/tests/src/com/android/contacts/EntityDeltaTests.java
+++ b/tests/src/com/android/contacts/EntityDeltaTests.java
@@ -367,7 +367,7 @@
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
         source.buildAssert(diff);
         source.buildDiff(diff);
-        assertEquals("Unexpected operations", 1, diff.size());
+        assertEquals("Unexpected operations", 2, diff.size());
         {
             final ContentProviderOperation oper = diff.get(0);
             assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
@@ -395,7 +395,7 @@
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
         source.buildAssert(diff);
         source.buildDiff(diff);
-        assertEquals("Unexpected operations", 2, diff.size());
+        assertEquals("Unexpected operations", 3, diff.size());
         {
             final ContentProviderOperation oper = diff.get(0);
             assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/EntityModifierTests.java
index 4bc4622..18877a3 100644
--- a/tests/src/com/android/contacts/EntityModifierTests.java
+++ b/tests/src/com/android/contacts/EntityModifierTests.java
@@ -512,7 +512,7 @@
         // Build diff, expecting single insert
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
         state.buildDiff(diff);
-        assertEquals("Unexpected operations", 1, diff.size());
+        assertEquals("Unexpected operations", 2, diff.size());
         {
             final ContentProviderOperation oper = diff.get(0);
             assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
@@ -540,7 +540,7 @@
         // Build diff, expecting two insert operations
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
         state.buildDiff(diff);
-        assertEquals("Unexpected operations", 2, diff.size());
+        assertEquals("Unexpected operations", 3, diff.size());
         {
             final ContentProviderOperation oper = diff.get(0);
             assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
diff --git a/tests/src/com/android/contacts/EntitySetTests.java b/tests/src/com/android/contacts/EntitySetTests.java
index c9fc3fa..edfca6d 100644
--- a/tests/src/com/android/contacts/EntitySetTests.java
+++ b/tests/src/com/android/contacts/EntitySetTests.java
@@ -486,6 +486,7 @@
                 buildAssertVersion(VER_FIRST),
                 buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert),
                 buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert),
+                buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT),
                 buildUpdateAggregationKeepTogether(CONTACT_BOB));
 
         // Merge in the second version, verify that our insert remains
@@ -495,6 +496,7 @@
                 buildAssertVersion(VER_SECOND),
                 buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert),
                 buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert),
+                buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT),
                 buildUpdateAggregationKeepTogether(CONTACT_BOB));
     }
 
@@ -545,6 +547,7 @@
                 buildAssertVersion(VER_SECOND),
                 buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, contactInsert),
                 buildOper(Data.CONTENT_URI, TYPE_INSERT, phoneInsert),
+                buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT),
                 buildUpdateAggregationKeepTogether(CONTACT_BOB));
     }