Merge change I394867d3 into eclair
* changes:
Cancel the selection and move the cursor pass the wait/pause symbol.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 68751db..b6a2045 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -195,11 +195,6 @@
</intent-filter>
<intent-filter>
- <action android:name="com.android.contacts.action.JOIN_AGGREGATE" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
-
- <intent-filter>
<action android:name="android.intent.action.INSERT_OR_EDIT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/person" />
@@ -239,6 +234,18 @@
/>
</activity>
+ <!-- An activity for joining contacts -->
+ <activity android:name="ContactsListActivity$JoinContactActivity"
+ android:theme="@style/TallTitleBarTheme"
+ android:clearTaskOnLaunch="true"
+ >
+ <intent-filter>
+ <action android:name="com.android.contacts.action.JOIN_AGGREGATE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+
<!-- Used to select display and sync groups -->
<activity android:name=".ui.DisplayGroupsActivity" android:label="@string/displayGroups" />
@@ -419,5 +426,7 @@
<activity android:name=".ImportVCardActivity"
android:theme="@style/BackgroundOnly" />
+ <activity android:name=".ExportVCardActivity"
+ android:theme="@style/BackgroundOnly" />
</application>
</manifest>
diff --git a/res/drawable-hdpi/ic_launcher_contacts.png b/res/drawable-hdpi/ic_launcher_contacts.png
new file mode 100644
index 0000000..73f5451
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_contacts.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_folder_live_contacts_phone.png b/res/drawable-hdpi/ic_launcher_folder_live_contacts_phone.png
new file mode 100644
index 0000000..70ce098
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_folder_live_contacts_phone.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_folder_live_contacts_starred.png b/res/drawable-hdpi/ic_launcher_folder_live_contacts_starred.png
new file mode 100644
index 0000000..a0c01ce
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_folder_live_contacts_starred.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_phone.png b/res/drawable-hdpi/ic_launcher_phone.png
new file mode 100644
index 0000000..47186c3
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/drawable/section_dark.9.png b/res/drawable/section_dark.9.png
new file mode 100644
index 0000000..5d1e1ae
--- /dev/null
+++ b/res/drawable/section_dark.9.png
Binary files differ
diff --git a/res/layout-finger/contact_card_layout.xml b/res/layout-finger/contact_card_layout.xml
index 3f090f5..fe931ec 100644
--- a/res/layout-finger/contact_card_layout.xml
+++ b/res/layout-finger/contact_card_layout.xml
@@ -36,6 +36,16 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
+ <TextView android:id="@+id/account_name"
+ style="?android:attr/listSeparatorTextViewStyle"
+ android:textColor="@*android:color/dim_foreground_dark"
+ android:textSize="12sp"
+ android:textStyle="normal"
+ android:background="@drawable/section_dark"
+ android:paddingLeft="7dp"
+ android:gravity="left|center_vertical"
+ android:visibility="gone" />
+
<FrameLayout android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="0dip"
diff --git a/res/layout-finger/contacts_list_content_join.xml b/res/layout-finger/contacts_list_content_join.xml
new file mode 100644
index 0000000..ce82d2c
--- /dev/null
+++ b/res/layout-finger/contacts_list_content_join.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:background="@*android:drawable/title_bar_medium"
+ android:padding="5dip"
+ android:gravity="center_vertical"
+ >
+
+ <ImageView
+ android:layout_width="48dip"
+ android:layout_height="48dip"
+ android:src="@drawable/ic_menu_merge"
+ android:gravity="center"
+ android:scaleType="fitCenter"
+ />
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="10dip">
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/titleJoinContactDataWith"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ />
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/blurbJoinContactDataWith"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+ </LinearLayout>
+ </LinearLayout>
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:fastScrollEnabled="true"
+ />
+</LinearLayout>
+
diff --git a/res/layout-finger/total_contacts.xml b/res/layout-finger/total_contacts.xml
new file mode 100644
index 0000000..1221ef3
--- /dev/null
+++ b/res/layout-finger/total_contacts.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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="@+id/totalContactsText"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="?android:attr/listSeparatorTextViewStyle"
+ android:textColor="@*android:color/dim_foreground_dark"
+ android:textSize="12sp"
+ android:textStyle="normal"
+ android:background="@drawable/section_dark"
+ android:paddingLeft="7dp"
+ android:gravity="left|center_vertical"
+ android:visibility="gone"
+/>
\ No newline at end of file
diff --git a/res/layout/act_edit.xml b/res/layout/act_edit.xml
index 250a0e1..47f1454 100644
--- a/res/layout/act_edit.xml
+++ b/res/layout/act_edit.xml
@@ -37,6 +37,16 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
+ <TextView android:id="@+id/account_name"
+ style="?android:attr/listSeparatorTextViewStyle"
+ android:textColor="@*android:color/dim_foreground_dark"
+ android:textSize="12sp"
+ android:textStyle="normal"
+ android:background="@drawable/section_dark"
+ android:paddingLeft="7dp"
+ android:gravity="left|center_vertical"
+ android:visibility="gone" />
+
<include
android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 1af6162..cf1d419 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -42,8 +42,7 @@
<item type="id" name="dialog_error_with_message" />
<!-- For ExportVCard -->
- <item type="id" name="dialog_confirm_export_vcard" />
- <item type="id" name="dialog_exporting_vcard" />
+ <item type="id" name="dialog_export_confirmation" />
<item type="id" name="dialog_fail_to_export_with_reason" />
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2cbfb02..3fdcf76 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -130,7 +130,7 @@
<!-- Menu item that joins an aggregate with another aggregate -->
<string name="menu_joinAggregate">Join</string>
-
+
<!-- Menu item to toggle the tabs that show where the contact data comes from. -->
<string name="menu_showSources">Show sources</string>
<string name="menu_hideSources">Hide sources</string>
@@ -138,6 +138,13 @@
<!-- Activity title for the Join Contact list -->
<string name="titleJoinAggregate">Join contact</string>
+ <!-- Heading of the Join Contact screen -->
+ <string name="titleJoinContactDataWith">Join contacts</string>
+
+ <!-- Info blurb on the Join Contact screen -->
+ <string name="blurbJoinContactDataWith">Contact data will be combined with
+ another contact selected from the list.</string>
+
<!-- List separator for the Join Contact list: Suggestions -->
<string name="separatorJoinAggregateSuggestions">Suggestions</string>
@@ -301,6 +308,9 @@
<!-- The text displayed when the contacts list is empty while displaying all contacts -->
<string name="noContacts">No contacts.</string>
+ <!-- The text displayed when the contacts list is empty while displaying results after searching contacts -->
+ <string name="noMatchingContacts">No matching contacts found.</string>
+
<!-- The text displayed when the contacts list is empty while displaying only contacts that have phone numbers -->
<string name="noContactsWithPhoneNumbers">No contacts with phone numbers.</string>
@@ -324,7 +334,7 @@
<string name="showFilterPhonesDescrip">Only display contacts that have phone numbers</string>
<!-- The header over the list of all contacts groups -->
- <string name="headerContactGroups">Contact groups</string>
+ <string name="headerContactGroups">Choose contacts to display</string>
<!-- The description of a group with the total number of contacts -->
<plurals name="groupDescrip">
@@ -351,8 +361,8 @@
<!-- Displayed in a spinner dialog after the user creates a contact and it's being saved to the database -->
<string name="savingContact">Saving contact\u2026</string>
- <!-- Displayed in a spinner dialog as user changes to display groups are saved -->
- <string name="savingDisplayGroups">Saving display groups\u2026</string>
+ <!-- Displayed in a spinner dialog as user changes to display options are saved -->
+ <string name="savingDisplayGroups">Saving display options\u2026</string>
<!-- Toast displayed when a contact is created -->
<string name="contactCreatedToast">Contact created.</string>
@@ -405,6 +415,12 @@
<!-- Section header in the Edit Contacts screen for the "Add more items" button -->
<string name="listSeparatorMore_edit">More</string>
+ <!-- Displayed at the top of the contacts showing the total number of contacts visible when "Only contacts with phones" is selected -->
+ <string name="listTotalPhoneContacts"><xliff:g id="num">%s</xliff:g> contacts with phone numbers</string>
+
+ <!-- Displayed at the top of the contacts showing the total number of contacts visible when "Only contacts with phones" not selected -->
+ <string name="listTotalAllContacts"><xliff:g id="num">%s</xliff:g> contacts</string>
+
<!-- The description text for the social activity stream tab. Space is limited for this string, so the shorter the better -->
<string name="socialStreamIconLabel">Social</string>
@@ -810,6 +826,12 @@
<!-- Message while reading multiple vCard files "(current number) of (total number) files" The order of "current number" and "total number" cannot be changed (like "total: (total number), current: (current number)")-->
<string name="reading_vcard_files"><xliff:g id="current_number">%s</xliff:g> of <xliff:g id="total_number">%s</xliff:g> files</string>
+ <!-- The message for exporting all contacts in the phone, without caring its Account -->
+ <string name="export_all_contacts">All contacts</string>
+
+ <!-- The message for exporting only contacts is the phone locally, which are not associated with any account -->
+ <string name="export_phone_local_only">Locally stored contacts</string>
+
<!-- The menu item that launches VCard export activity -->
<string name="export_contact_list">Export contacts</string>
@@ -825,6 +847,9 @@
<!-- Dialog message shown when exporting Contact data failed -->
<string name="exporting_contact_failed_message">Failed to export contact data.\nReason for failure: \"<xliff:g id="fail_reason">%s</xliff:g>\"</string>
+ <!-- The failed reason: "There is no exportable contact" -->
+ <string name="fail_reason_no_exportable_contact">There is no exportable contact</string>
+
<!-- The failed reason: "Too many vcard files on the SD Card" -->
<string name="fail_reason_too_many_vcard">Too many vCard files on the SD card</string>
@@ -846,6 +871,15 @@
<!-- The failed reason: "Error occured during export" -->
<string name="fail_reason_error_occurred_during_export">Error occured during export: \"<xliff:g id="exact_reason">%s</xliff:g>\"</string>
+ <!-- The error reason the vCard composer emits: "Failed to get database information" -->
+ <string name="composer_failed_to_get_database_infomation">Failed to get database information</string>
+
+ <!-- The error reason the vCard composer emits: "There is no exportable contact. You might choose un-exportable data" -->
+ <string name="composer_has_no_exportable_contact">There is no exportable contact. You might choose un-exportable data</string>
+
+ <!-- The error reason the vCard composer emits: "The vCard composer object is not correctly initialized" -->
+ <string name="composer_not_initialized">The vCard composer is not correctly initialized</string>
+
<!-- The failed reason: "Could not open a specific file" -->
<string name="fail_reason_could_not_open_file">Could not open \"<xliff:g id="file_name">%s</xliff:g>\": <xliff:g id="exact_reason">%s</xliff:g></string>
@@ -1112,10 +1146,13 @@
<string name="name_phonetic_middle">Phonetic middle name</string>
<!-- Field title for the phonetic family name of a contact -->
<string name="name_phonetic_family">Phonetic family name</string>
-
+
<!-- The title for the action to remove a contact from an aggregated contact -->
<string name="split_label">Split</string>
<!-- The explanation of what "split" will do. This needs word-smithing. -->
<string name="split_explanation">Make this data its own contact.</string>
+
+ <!-- Formatting string for account name -->
+ <string name="account_name_format">From account: <xliff:g id="account" example="user@gmail.com">%s</xliff:g></string>
</resources>
diff --git a/src/com/android/contacts/Collapser.java b/src/com/android/contacts/Collapser.java
index db1da1f..3872dfd 100644
--- a/src/com/android/contacts/Collapser.java
+++ b/src/com/android/contacts/Collapser.java
@@ -39,38 +39,42 @@
*/
public interface Collapsible<T> {
public boolean collapseWith(T t);
- public String getCollapseKey();
+ public boolean shouldCollapseWith(T t);
}
/**
* Collapses a list of Collapsible items into a list of collapsed items. Items are collapsed
- * if they produce equal collapseKeys {@Link Collapsible#getCollapseKey()}, and are collapsed
- * through the {@Link Collapsible#doCollapseWith(Object)} function implemented by the data item.
+ * if {@link Collapsible#shouldCollapseWith(Object) return strue, and are collapsed
+ * through the {@Link Collapsible#collapseWith(Object)} function implemented by the data item.
*
* @param list ArrayList of Objects of type <T extends Collapsible<T>> to be collapsed.
*/
public static <T extends Collapsible<T>> void collapseList(ArrayList<T> list) {
- HashMap<String, T> collapseMap = new HashMap<String, T>();
- ArrayList<String> collapseKeys = new ArrayList<String>();
int listSize = list.size();
- for (int j = 0; j < listSize; j++) {
- T entry = list.get(j);
- String collapseKey = entry.getCollapseKey();
- if (!collapseMap.containsKey(collapseKey)) {
- collapseMap.put(collapseKey, entry);
- collapseKeys.add(collapseKey);
- } else {
- collapseMap.get(collapseKey).collapseWith(entry);
+
+ for (int i = 0; i < listSize; i++) {
+ T iItem = list.get(i);
+ if (iItem != null) {
+ for (int j = i + 1; j < listSize; j++) {
+ T jItem = list.get(j);
+ if (jItem != null) {
+ if (iItem.shouldCollapseWith(jItem)) {
+ iItem.collapseWith(jItem);
+ list.set(j, null);
+ }
+ }
+ }
}
}
- if (collapseKeys.size() < listSize) {
- list.clear();
- Iterator<String> itr = collapseKeys.iterator();
- while (itr.hasNext()) {
- list.add(collapseMap.get(itr.next()));
+ // Remove the null items
+ Iterator<T> itr = list.iterator();
+ while (itr.hasNext()) {
+ if (itr.next() == null) {
+ itr.remove();
}
}
+
}
}
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 072725a..6d2ea57 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -16,11 +16,10 @@
package com.android.contacts;
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.GoogleSource;
import com.android.contacts.model.Sources;
import com.android.contacts.ui.DisplayGroupsActivity;
import com.android.contacts.ui.DisplayGroupsActivity.Prefs;
+import com.android.contacts.util.AccountSelectionUtil;
import com.android.contacts.util.Constants;
import android.accounts.Account;
@@ -117,8 +116,13 @@
/**
* Displays a list of contacts. Usually is embedded into the ContactsActivity.
*/
-public final class ContactsListActivity extends ListActivity implements
+public class ContactsListActivity extends ListActivity implements
View.OnCreateContextMenuListener, View.OnClickListener {
+
+ public static class JoinContactActivity extends ContactsListActivity {
+
+ }
+
private static final String TAG = "ContactsListActivity";
private static final boolean ENABLE_ACTION_ICON_OVERLAYS = true;
@@ -175,11 +179,13 @@
static final int MODE_MASK_SHOW_CALL_BUTTON = 0x02000000;
/** Mask to disable fasttrack (images will show as normal images) */
static final int MODE_MASK_DISABLE_FASTTRACK = 0x01000000;
+ /** Mask to show the total number of contacts at the top */
+ static final int MODE_MASK_SHOW_NUMBER_OF_CONTACTS = 0x00800000;
/** Unknown mode */
static final int MODE_UNKNOWN = 0;
/** Default mode */
- static final int MODE_DEFAULT = 4 | MODE_MASK_SHOW_PHOTOS;
+ static final int MODE_DEFAULT = 4 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
/** Custom mode */
static final int MODE_CUSTOM = 8;
/** Show all starred contacts */
@@ -301,15 +307,6 @@
static final String KEY_PICKER_MODE = "picker_mode";
- /*
- * TODO: Will be commented in the really near future. Similar commented out codes will be done.
- * These are for make vCard exporter code understard two additional options:
- * "export contacts in the phone only" and
- * "export all contacts without caring Account information".
- * static final Account sExportPhoneLocalAccount;
- * static final Account sExportAllAccount;
- */
-
private ContactItemListAdapter mAdapter;
int mMode = MODE_DEFAULT;
@@ -351,8 +348,6 @@
*/
private String mQueryData;
- private VCardExporter mVCardExporter;
-
private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
private static final String CLAUSE_ONLY_PHONES = Contacts.HAS_PHONE_NUMBER + "=1";
@@ -362,8 +357,6 @@
static {
sContactsIdMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sContactsIdMatcher.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
- /*sExportPhoneLocalAccount = new Account("Phone-local Account", "phone-local");
- sExportAllAccount = new Account("All account", "all");*/
}
private class DeleteClickListener implements DialogInterface.OnClickListener {
@@ -394,8 +387,6 @@
final String action = intent.getAction();
mMode = MODE_UNKNOWN;
- setContentView(R.layout.contacts_list_content);
-
Log.i(TAG, "Called with action: " + action);
if (UI.LIST_DEFAULT.equals(action)) {
mMode = MODE_DEFAULT;
@@ -541,6 +532,7 @@
return;
}
+
if (JOIN_AGGREGATE.equals(action)) {
mMode = MODE_JOIN_CONTACT;
mQueryAggregateId = intent.getLongExtra(EXTRA_AGGREGATE_ID, -1);
@@ -550,16 +542,21 @@
setResult(RESULT_CANCELED);
finish();
}
-
- setTitle(R.string.titleJoinAggregate);
}
if (mMode == MODE_UNKNOWN) {
mMode = MODE_DEFAULT;
}
+ if (mMode == MODE_JOIN_CONTACT) {
+ setContentView(R.layout.contacts_list_content_join);
+ } else {
+ setContentView(R.layout.contacts_list_content);
+ }
+
// Setup the UI
final ListView list = getListView();
+
// Tell list view to not show dividers. We'll do it ourself so that we can *not* show
// them when an A-Z headers is visible.
list.setDividerHeight(0);
@@ -569,9 +566,14 @@
list.setTextFilterEnabled(true);
}
+ final LayoutInflater inflater = getLayoutInflater();
+ if ((mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0) {
+ View totalContacts = inflater.inflate(R.layout.total_contacts, list, false);
+ list.addHeaderView(totalContacts);
+ }
+
if ((mMode & MODE_MASK_CREATE_NEW) != 0) {
// Add the header for creating a new contact
- final LayoutInflater inflater = getLayoutInflater();
View header = inflater.inflate(R.layout.create_new_contact, list, false);
list.addHeaderView(header);
}
@@ -583,6 +585,11 @@
setListAdapter(mAdapter);
getListView().setOnScrollListener(mAdapter);
+ if ((mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0) {
+ TextView totalContacts = (TextView) findViewById(R.id.totalContactsText);
+ totalContacts.setVisibility(View.VISIBLE);
+ }
+
// We manually save/restore the listview state
list.setSaveEnabled(false);
@@ -625,6 +632,10 @@
}
private void setEmptyText() {
+ if (mMode == MODE_JOIN_CONTACT) {
+ return;
+ }
+
TextView empty = (TextView) findViewById(R.id.emptyText);
int gravity = Gravity.NO_GRAVITY;
@@ -633,6 +644,8 @@
gravity = Gravity.CENTER;
} else if (mMode == MODE_STREQUENT || mMode == MODE_STARRED) {
empty.setText(getText(R.string.noFavoritesHelpText));
+ } else if (mMode == MODE_QUERY) {
+ empty.setText(getText(R.string.noMatchingContacts));
} else {
boolean hasSim = ((TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE))
.hasIccCard();
@@ -747,6 +760,7 @@
// be there and show up while the new query is happening. After the async query finished
// in response to onRestart() setLoading(false) will be called.
mAdapter.setLoading(true);
+ mAdapter.setSuggestionsCursor(null);
mAdapter.changeCursor(null);
mAdapter.clearImageFetching();
@@ -755,21 +769,6 @@
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
searchManager.stopSearch();
}
-
- // When the orientation is changed or Home button is pressed, onStop() is called.
- // Then, we stop exporting just for safety.
- //
- // Technically, it is because the dialog displaying the current status of export is
- // closed on this method call and we cannot reliably restore the dialog in the current
- // implementation, while the thread for exporting vCard is working at that time, without
- // showing its status to users :(
- // Also, it is probably not a strong requirment for us to both
- // - enable users to press Home, slide hardware keyboard during the export
- // - and also let vCard export to keep working.
- if (mVCardExporter != null) {
- mVCardExporter.cancelExport();
- mVCardExporter = null;
- }
}
@Override
@@ -812,7 +811,7 @@
return true;
}
case R.id.menu_import_export: {
- showDialog(R.id.dialog_import_export);
+ displayImportExportDialog();
return true;
}
case R.id.menu_accounts: {
@@ -830,30 +829,9 @@
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
- case R.id.dialog_import_export: {
- return createImportExportDialog();
- }
case R.string.import_from_sim:
- case R.string.import_from_sdcard:
- case R.string.export_to_sdcard: {
- return createSelectAccountDialog(id);
- }
- case R.string.fail_reason_too_many_vcard: {
- return new AlertDialog.Builder(this)
- .setTitle(R.string.exporting_contact_failed_title)
- .setMessage(getString(R.string.exporting_contact_failed_message,
- getString(R.string.fail_reason_too_many_vcard)))
- .setPositiveButton(android.R.string.ok, null)
- .create();
- }
- case R.id.dialog_confirm_export_vcard: {
- return mVCardExporter.getExportConfirmationDialog();
- }
- case R.id.dialog_exporting_vcard: {
- return mVCardExporter.getExportingVCardDialog();
- }
- case R.id.dialog_fail_to_export_with_reason: {
- return mVCardExporter.getErrorDialogWithReason();
+ case R.string.import_from_sdcard: {
+ return AccountSelectionUtil.getSelectAccountDialog(this, id);
}
case R.id.dialog_sdcard_not_found: {
AlertDialog.Builder builder = new AlertDialog.Builder(this)
@@ -866,104 +844,11 @@
return super.onCreateDialog(id);
}
- private class AccountSelectedListener
- implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
-
- final private List<Account> mAccountList;
- final private int mResId;
-
- public AccountSelectedListener(List<Account> accountList, int resId) {
- if (accountList == null || accountList.size() == 0) {
- Log.e(TAG, "The size of Account list is 0.");
- }
- mAccountList = accountList;
- mResId = resId;
- }
-
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- switch (mResId) {
- case R.string.import_from_sim: {
- doImportFromSim(mAccountList.get(which));
- break;
- }
- case R.string.import_from_sdcard: {
- doImportFromSdCard(mAccountList.get(which));
- break;
- }
- /*case R.string.export_to_sdcard: {
- }*/
- }
- }
-
- public void onCancel(DialogInterface dialog) {
- dialog.dismiss();
- }
- }
-
- private Dialog createSelectAccountDialog(int resId) {
- final boolean isExport = (resId == R.string.export_to_sdcard);
- final boolean displayWritableOnly = !isExport;
-
- final Sources sources = Sources.getInstance(this);
- final List<Account> accountList = sources.getAccounts(displayWritableOnly);
-
- /*if (isExport) {
- accountList.add(sExportPhoneLocalAccount);
- accountList.add(sExportAllAccount);
- }*/
-
- // Assume accountList.size() > 1
-
- // Wrap our context to inflate list items using correct theme
- final Context dialogContext = new ContextThemeWrapper(
- this, android.R.style.Theme_Light);
- final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- final ArrayAdapter<Account> accountAdapter =
- new ArrayAdapter<Account>(this, android.R.layout.simple_list_item_2, accountList) {
-
- @Override
- public View getView(int position, View convertView,
- ViewGroup parent) {
- if (convertView == null) {
- convertView = dialogInflater.inflate(
- android.R.layout.simple_list_item_2,
- parent, false);
- }
-
- // TODO: show icon along with title
- final TextView text1 =
- (TextView)convertView.findViewById(android.R.id.text1);
- final TextView text2 =
- (TextView)convertView.findViewById(android.R.id.text2);
-
- final Account account = this.getItem(position);
- final ContactsSource source =
- sources.getInflatedSource(account.type,
- ContactsSource.LEVEL_SUMMARY);
-
- text1.setText(account.name);
- text2.setText(source.getDisplayLabel(ContactsListActivity.this));
-
- return convertView;
- }
- };
-
- AccountSelectedListener listener = new AccountSelectedListener(accountList, resId);
- final AlertDialog.Builder builder =
- new AlertDialog.Builder(this)
- .setTitle(R.string.dialog_new_contact_account)
- .setSingleChoiceItems(accountAdapter, 0, listener)
- .setOnCancelListener(listener);
- return builder.create();
- }
-
/**
* Create a {@link Dialog} that allows the user to pick from a bulk import
* or bulk export task across all contacts.
*/
- private Dialog createImportExportDialog() {
+ private void displayImportExportDialog() {
// Wrap our context to inflate list items using correct theme
final Context dialogContext = new ContextThemeWrapper(this, android.R.style.Theme_Light);
final Resources res = dialogContext.getResources();
@@ -996,7 +881,8 @@
adapter.add(R.string.export_to_sdcard);
}
- final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
+ final DialogInterface.OnClickListener clickListener =
+ new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
@@ -1004,12 +890,13 @@
switch (resId) {
case R.string.import_from_sim:
case R.string.import_from_sdcard: {
- handleImportExportRequest(resId);
+ handleImportRequest(resId);
break;
}
case R.string.export_to_sdcard: {
- // TODO: use handleImportExportRequest()
- doExportToSdCard();
+ Context context = ContactsListActivity.this;
+ Intent exportIntent = new Intent(context, ExportVCardActivity.class);
+ context.startActivity(exportIntent);
break;
}
default: {
@@ -1020,93 +907,27 @@
}
};
- final AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.dialog_import_export);
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.setSingleChoiceItems(adapter, -1, clickListener);
- return builder.create();
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.dialog_import_export)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setSingleChoiceItems(adapter, -1, clickListener)
+ .show();
}
- private void handleImportExportRequest(int resId) {
- /*final boolean isExport = (resId == R.string.export_to_sdcard);
- if (isExport) {
- // We're sure there are at least two Account ("phone-local" and "all").
- // Go to account selection every time.
- showDialog(R.string.export_to_sdcard);
- return;
- }*/
-
- // As for import, there's three possibilities
+ private void handleImportRequest(int resId) {
+ // There's three possibilities:
// - more than one accounts -> ask the user
// - just one account -> use the account without asking the user
// - no account -> use phone-local storage without asking the user
-
final Sources sources = Sources.getInstance(this);
final List<Account> accountList = sources.getAccounts(true);
final int size = accountList.size();
- Account account;
if (size > 1) {
showDialog(resId);
return;
- } else if (size == 1) {
- account = accountList.get(0);
- } else {
- account = null;
- }
- switch (resId) {
- case R.string.import_from_sim: {
- doImportFromSim(account);
- break;
- }
- case R.string.import_from_sdcard: {
- doImportFromSdCard(account);
- break;
- }
- /*case R.string.export_to_sdcard: {
- doExportToSdCard(account);
- break;
- }*/
- }
- }
-
- private void doImportFromSim(Account account) {
- if (account != null) {
- GoogleSource.createMyContactsIfNotExist(account, this);
}
- Intent importIntent = new Intent(Intent.ACTION_VIEW);
- importIntent.setType("vnd.android.cursor.item/sim-contact");
- if (account != null) {
- importIntent.putExtra("account_name", account.name);
- importIntent.putExtra("account_type", account.type);
- }
- importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts");
- startActivity(importIntent);
- }
-
- private void doImportFromSdCard(Account account) {
- if (account != null) {
- GoogleSource.createMyContactsIfNotExist(account, this);
- }
-
- Intent importIntent = new Intent(this, ImportVCardActivity.class);
- if (account != null) {
- importIntent.putExtra("account_name", account.name);
- importIntent.putExtra("account_type", account.type);
- }
- startActivity(importIntent);
- }
-
- private void doExportToSdCard() {
- mVCardExporter = new VCardExporter(this);
- mVCardExporter.startExportVCardToSdCard();
- }
-
- /**
- * Used when VCardExporter finishes its exporting.
- */
- /* package */ void removeReferenceToVCardExporter() {
- mVCardExporter = null;
+ AccountSelectionUtil.doImport(this, resId, (size == 1 ? accountList.get(0) : null));
}
@Override
@@ -1273,6 +1094,13 @@
getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(mList.getWindowToken(), 0);
+ if ((mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0) {
+ if (position == 0) {
+ return;
+ }
+ position--;
+ }
+
if (mMode == MODE_INSERT_OR_EDIT_CONTACT) {
Intent intent;
if (position == 0) {
@@ -1795,7 +1623,7 @@
case MODE_JOIN_CONTACT:
mQueryHandler.setLoadingJoinSuggestions(true);
mQueryHandler.startQuery(QUERY_TOKEN, null, getJoinSuggestionsUri(null), projection,
- Contacts._ID + " != " + mQueryAggregateId, null, null);
+ null, null, null);
break;
}
}
@@ -1871,8 +1699,8 @@
null, null);
mAdapter.setSuggestionsCursor(cursor);
return resolver.query(getContactFilterUri(filter), projection,
- Contacts._ID + " != " + mQueryAggregateId, null,
- getSortOrder(projection));
+ Contacts._ID + " != " + mQueryAggregateId + " AND " + CLAUSE_ONLY_VISIBLE,
+ null, getSortOrder(projection));
}
}
throw new UnsupportedOperationException("filtering not allowed in mode " + mMode);
@@ -1929,7 +1757,7 @@
getColumnIndex(Phone.IS_SUPER_PRIMARY)) != 0) {
// Found super primary, call it.
phone = phonesCursor.
- getString(phonesCursor.getColumnIndex(Phone.NUMBER));
+ getString(phonesCursor.getColumnIndex(Phone.NUMBER));
break;
}
}
@@ -2012,7 +1840,8 @@
startQuery(QUERY_TOKEN, null, activity.getContactFilterUri(activity.mQuery),
CONTACTS_SUMMARY_PROJECTION,
- Contacts._ID + " != " + activity.mQueryAggregateId, null,
+ Contacts._ID + " != " + activity.mQueryAggregateId
+ + " AND " + CLAUSE_ONLY_VISIBLE, null,
getSortOrder(CONTACTS_SUMMARY_PROJECTION));
return;
}
@@ -2534,7 +2363,9 @@
// Get the split between starred and frequent items, if the mode is strequent
mFrequentSeparatorPos = ListView.INVALID_POSITION;
- if (cursor != null && cursor.getCount() > 0 && mMode == MODE_STREQUENT) {
+ int cursorCount = 0;
+ if (cursor != null && (cursorCount = cursor.getCount()) > 0
+ && mMode == MODE_STREQUENT) {
cursor.move(-1);
for (int i = 0; cursor.moveToNext(); i++) {
int starred = cursor.getInt(SUMMARY_STARRED_COLUMN_INDEX);
@@ -2549,7 +2380,12 @@
}
super.changeCursor(cursor);
-
+ if ((mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0) {
+ TextView totalContacts = (TextView) findViewById(R.id.totalContactsText);
+ int stringId = mDisplayOnlyPhones
+ ? R.string.listTotalPhoneContacts : R.string.listTotalAllContacts;
+ totalContacts.setText(getString(stringId, cursorCount));
+ }
// Update the indexer for the fast scroll widget
updateIndexer(cursor);
}
diff --git a/src/com/android/contacts/ExportVCardActivity.java b/src/com/android/contacts/ExportVCardActivity.java
new file mode 100644
index 0000000..08f43c1
--- /dev/null
+++ b/src/com/android/contacts/ExportVCardActivity.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2009 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.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.pim.vcard.VCardComposer;
+import android.provider.ContactsContract.Contacts;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ExportVCardActivity extends Activity {
+ private static final String LOG_TAG = "ExportVCardActivity";
+
+ // If true, VCardExporter is able to emits files longer than 8.3 format.
+ private static final boolean ALLOW_LONG_FILE_NAME = false;
+ private String mTargetDirectory;
+ private String mFileNamePrefix;
+ private String mFileNameSuffix;
+ private int mFileIndexMinimum;
+ private int mFileIndexMaximum;
+ private String mFileNameExtension;
+ private String mVCardTypeStr;
+ private Set<String> mExtensionsToConsider;
+
+ private ProgressDialog mProgressDialog;
+
+ private Handler mHandler = new Handler();
+
+ // Used temporaly when asking users to confirm the file name
+ private String mTargetFileName;
+
+ // String for storing error reason temporaly.
+ private String mErrorReason;
+
+ private ActualExportThread mActualExportThread;
+
+ private class CancelListener
+ implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ }
+
+ private CancelListener mCancelListener = new CancelListener();
+
+ private class ErrorReasonDisplayer implements Runnable {
+ private final int mResId;
+ public ErrorReasonDisplayer(int resId) {
+ mResId = resId;
+ }
+ public ErrorReasonDisplayer(String errorReason) {
+ mResId = R.id.dialog_fail_to_export_with_reason;
+ mErrorReason = errorReason;
+ }
+ public void run() {
+ // Show the Dialog only when the parent Activity is still alive.
+ if (!ExportVCardActivity.this.isFinishing()) {
+ showDialog(mResId);
+ }
+ }
+ }
+
+ private class ExportConfirmationListener implements DialogInterface.OnClickListener {
+ private final String mFileName;
+
+ public ExportConfirmationListener(String fileName) {
+ mFileName = fileName;
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ mActualExportThread = new ActualExportThread(mFileName);
+ String title = getString(R.string.exporting_contact_list_title);
+ String message = getString(R.string.exporting_contact_list_message, mFileName);
+ mProgressDialog = new ProgressDialog(ExportVCardActivity.this);
+ mProgressDialog.setTitle(title);
+ mProgressDialog.setMessage(message);
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ mProgressDialog.setOnCancelListener(mActualExportThread);
+ mProgressDialog.show();
+ mActualExportThread.start();
+ }
+ }
+ }
+
+ private class ActualExportThread extends Thread
+ implements DialogInterface.OnCancelListener {
+ private PowerManager.WakeLock mWakeLock;
+ private String mFileName;
+ private boolean mCanceled = false;
+
+ public ActualExportThread(String fileName) {
+ mFileName = fileName;
+ PowerManager powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(
+ PowerManager.SCREEN_DIM_WAKE_LOCK |
+ PowerManager.ON_AFTER_RELEASE, LOG_TAG);
+ }
+
+ @Override
+ public void run() {
+ boolean shouldCallFinish = true;
+ mWakeLock.acquire();
+ VCardComposer composer = null;
+ try {
+ OutputStream outputStream = null;
+ try {
+ outputStream = new FileOutputStream(mFileName);
+ } catch (FileNotFoundException e) {
+ final String errorReason =
+ getString(R.string.fail_reason_could_not_open_file,
+ mFileName, e.getMessage());
+ shouldCallFinish = false;
+ mHandler.post(new ErrorReasonDisplayer(errorReason));
+ return;
+ }
+
+ composer = new VCardComposer(ExportVCardActivity.this, mVCardTypeStr, true);
+ // composer = new VCardComposer(ExportVCardActivity,
+ // VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8, true);
+ composer.addHandler(composer.new HandlerForOutputStream(outputStream));
+
+ if (!composer.init()) {
+ final String errorReason = composer.getErrorReason();
+ Log.e(LOG_TAG, "initialization of vCard composer failed: " + errorReason);
+ final String translatedErrorReason =
+ translateComposerError(errorReason);
+ mHandler.post(new ErrorReasonDisplayer(
+ getString(R.string.fail_reason_could_not_initialize_exporter,
+ translatedErrorReason)));
+ shouldCallFinish = false;
+ return;
+ }
+
+ int size = composer.getCount();
+
+ if (size == 0) {
+ mHandler.post(new ErrorReasonDisplayer(
+ getString(R.string.fail_reason_no_exportable_contact)));
+ shouldCallFinish = false;
+ return;
+ }
+
+ mProgressDialog.setProgressNumberFormat(
+ getString(R.string.exporting_contact_list_progress));
+ mProgressDialog.setMax(size);
+ mProgressDialog.setProgress(0);
+
+ while (!composer.isAfterLast()) {
+ if (mCanceled) {
+ return;
+ }
+ if (!composer.createOneEntry()) {
+ final String errorReason = composer.getErrorReason();
+ Log.e(LOG_TAG, "Failed to read a contact: " + errorReason);
+ final String translatedErrorReason =
+ translateComposerError(errorReason);
+ mHandler.post(new ErrorReasonDisplayer(
+ getString(R.string.fail_reason_error_occurred_during_export,
+ translatedErrorReason)));
+ shouldCallFinish = false;
+ return;
+ }
+ mProgressDialog.incrementProgressBy(1);
+ }
+ } finally {
+ if (composer != null) {
+ composer.terminate();
+ }
+ mWakeLock.release();
+ mProgressDialog.dismiss();
+ if (shouldCallFinish && !isFinishing()) {
+ finish();
+ }
+ }
+ }
+
+ @Override
+ public void finalize() {
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+
+ public void cancel() {
+ mCanceled = true;
+ }
+
+ public void onCancel(DialogInterface dialog) {
+ cancel();
+ }
+ }
+
+ private String translateComposerError(String errorMessage) {
+ Resources resources = getResources();
+ if (VCardComposer.FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO.equals(errorMessage)) {
+ return resources.getString(R.string.composer_failed_to_get_database_infomation);
+ } else if (VCardComposer.FAILURE_REASON_NO_ENTRY.equals(errorMessage)) {
+ return resources.getString(R.string.composer_has_no_exportable_contact);
+ } else if (VCardComposer.FAILURE_REASON_NOT_INITIALIZED.equals(errorMessage)) {
+ return resources.getString(R.string.composer_not_initialized);
+ } else {
+ return errorMessage;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ mTargetDirectory = getString(R.string.config_export_dir);
+ mFileNamePrefix = getString(R.string.config_export_file_prefix);
+ mFileNameSuffix = getString(R.string.config_export_file_suffix);
+ mFileNameExtension = getString(R.string.config_export_file_extension);
+ mVCardTypeStr = getString(R.string.config_export_vcard_type);
+
+ mExtensionsToConsider = new HashSet<String>();
+ mExtensionsToConsider.add(mFileNameExtension);
+
+ final String additionalExtensions =
+ getString(R.string.config_export_extensions_to_consider);
+ if (!TextUtils.isEmpty(additionalExtensions)) {
+ for (String extension : additionalExtensions.split(",")) {
+ String trimed = extension.trim();
+ if (trimed.length() > 0) {
+ mExtensionsToConsider.add(trimed);
+ }
+ }
+ }
+
+ final Resources resources = getResources();
+ mFileIndexMinimum = resources.getInteger(R.integer.config_export_file_min_index);
+ mFileIndexMaximum = resources.getInteger(R.integer.config_export_file_max_index);
+
+ startExportVCardToSdCard();
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case R.id.dialog_export_confirmation: {
+ return getExportConfirmationDialog();
+ }
+ case R.string.fail_reason_too_many_vcard: {
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.exporting_contact_failed_title)
+ .setMessage(getString(R.string.exporting_contact_failed_message,
+ getString(R.string.fail_reason_too_many_vcard)))
+ .setPositiveButton(android.R.string.ok, mCancelListener)
+ .create();
+ }
+ case R.id.dialog_fail_to_export_with_reason: {
+ return getErrorDialogWithReason();
+ }
+ case R.id.dialog_sdcard_not_found: {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setTitle(R.string.no_sdcard_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.no_sdcard_message)
+ .setPositiveButton(android.R.string.ok, mCancelListener);
+ }
+ }
+ return super.onCreateDialog(id);
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ if (id == R.id.dialog_fail_to_export_with_reason) {
+ ((AlertDialog)dialog).setMessage(getErrorReason());
+ } else if (id == R.id.dialog_export_confirmation) {
+ ((AlertDialog)dialog).setMessage(
+ getString(R.string.confirm_export_message, mTargetFileName));
+ } else {
+ super.onPrepareDialog(id, dialog);
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mActualExportThread != null) {
+ // The Activity is no longer visible. Stop the thread.
+ mActualExportThread.cancel();
+ mActualExportThread = null;
+ }
+
+ if (!isFinishing()) {
+ finish();
+ }
+ }
+
+ /**
+ * Tries to start exporting VCard. If there's no SDCard available,
+ * an error dialog is shown.
+ */
+ public void startExportVCardToSdCard() {
+ File targetDirectory = new File(mTargetDirectory);
+
+ if (!(targetDirectory.exists() &&
+ targetDirectory.isDirectory() &&
+ targetDirectory.canRead()) &&
+ !targetDirectory.mkdirs()) {
+ showDialog(R.id.dialog_sdcard_not_found);
+ } else {
+ mTargetFileName = getAppropriateFileName(mTargetDirectory);
+ if (TextUtils.isEmpty(mTargetFileName)) {
+ mTargetFileName = null;
+ // finish() is called via the error dialog. Do not call the method here.
+ return;
+ }
+
+ showDialog(R.id.dialog_export_confirmation);
+ }
+ }
+
+ /**
+ * Tries to get an appropriate filename. Returns null if it fails.
+ */
+ private String getAppropriateFileName(final String destDirectory) {
+ int fileNumberStringLength = 0;
+ {
+ // Calling Math.Log10() is costly.
+ int tmp;
+ for (fileNumberStringLength = 0, tmp = mFileIndexMaximum; tmp > 0;
+ fileNumberStringLength++, tmp /= 10) {
+ }
+ }
+ String bodyFormat = "%s%0" + fileNumberStringLength + "d%s";
+
+ if (!ALLOW_LONG_FILE_NAME) {
+ String possibleBody = String.format(bodyFormat,mFileNamePrefix, 1, mFileNameSuffix);
+ if (possibleBody.length() > 8 || mFileNameExtension.length() > 3) {
+ Log.e(LOG_TAG, "This code does not allow any long file name.");
+ mErrorReason = getString(R.string.fail_reason_too_long_filename,
+ String.format("%s.%s", possibleBody, mFileNameExtension));
+ showDialog(R.id.dialog_fail_to_export_with_reason);
+ // finish() is called via the error dialog. Do not call the method here.
+ return null;
+ }
+ }
+
+ // Note that this logic assumes that the target directory is case insensitive.
+ // As of 2009-07-16, it is true since the external storage is only sdcard, and
+ // it is formated as FAT/VFAT.
+ // TODO: fix this.
+ for (int i = mFileIndexMinimum; i <= mFileIndexMaximum; i++) {
+ boolean numberIsAvailable = true;
+ // SD Association's specification seems to require this feature, though we cannot
+ // have the specification since it is proprietary...
+ String body = null;
+ for (String possibleExtension : mExtensionsToConsider) {
+ body = String.format(bodyFormat, mFileNamePrefix, i, mFileNameSuffix);
+ File file = new File(String.format("%s/%s.%s",
+ destDirectory, body, possibleExtension));
+ if (file.exists()) {
+ numberIsAvailable = false;
+ break;
+ }
+ }
+ if (numberIsAvailable) {
+ return String.format("%s/%s.%s", destDirectory, body, mFileNameExtension);
+ }
+ }
+ showDialog(R.string.fail_reason_too_many_vcard);
+ return null;
+ }
+
+ public Dialog getExportConfirmationDialog() {
+ if (TextUtils.isEmpty(mTargetFileName)) {
+ Log.e(LOG_TAG, "Target file name is empty, which must not be!");
+ // This situation is not acceptable (probably a bug!), but we don't have no reason to
+ // show...
+ mErrorReason = null;
+ return getErrorDialogWithReason();
+ }
+
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.confirm_export_title)
+ .setMessage(getString(R.string.confirm_export_message, mTargetFileName))
+ .setPositiveButton(android.R.string.ok,
+ new ExportConfirmationListener(mTargetFileName))
+ .setNegativeButton(android.R.string.cancel, mCancelListener)
+ .setOnCancelListener(mCancelListener)
+ .create();
+ }
+
+ public Dialog getErrorDialogWithReason() {
+ if (mErrorReason == null) {
+ Log.e(LOG_TAG, "Error reason must have been set.");
+ mErrorReason = getString(R.string.fail_reason_unknown);
+ }
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.exporting_contact_failed_title)
+ .setMessage(getString(R.string.exporting_contact_failed_message, mErrorReason))
+ .setPositiveButton(android.R.string.ok, mCancelListener)
+ .setOnCancelListener(mCancelListener)
+ .create();
+ }
+
+ public void cancelExport() {
+ if (mActualExportThread != null) {
+ mActualExportThread.cancel();
+ mActualExportThread = null;
+ }
+ }
+
+ public String getErrorReason() {
+ return mErrorReason;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index e77a3e8..64b5727 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -531,22 +531,14 @@
mProgressDialogForScanVCard = null;
if (mGotIOException) {
- mHandler.post(new Runnable() {
- public void run() {
- showDialog(R.id.dialog_io_exception);
- }
- });
+ mHandler.post(new DialogDisplayer(R.id.dialog_io_exception));
} else if (mCanceled) {
finish();
} else {
int size = mAllVCardFileList.size();
final Context context = ImportVCardActivity.this;
if (size == 0) {
- mHandler.post(new Runnable() {
- public void run() {
- showDialog(R.id.dialog_vcard_not_found);
- }
- });
+ mHandler.post(new DialogDisplayer(R.id.dialog_vcard_not_found));
} else {
startVCardSelectAndImport();
}
diff --git a/src/com/android/contacts/PhoneDisambigDialog.java b/src/com/android/contacts/PhoneDisambigDialog.java
index 58d3721..b727c77 100644
--- a/src/com/android/contacts/PhoneDisambigDialog.java
+++ b/src/com/android/contacts/PhoneDisambigDialog.java
@@ -17,8 +17,9 @@
package com.android.contacts;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.List;
+
+import com.android.contacts.Collapser.Collapsible;
import android.app.AlertDialog;
import android.content.ContentUris;
@@ -28,12 +29,13 @@
import android.database.Cursor;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.telephony.PhoneNumberUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
-import android.widget.SimpleCursorAdapter;
+import android.widget.ListAdapter;
/**
* Class used for displaying a dialog with a list of phone numbers of which
@@ -47,6 +49,8 @@
private AlertDialog mDialog;
private boolean mSendSms;
private Cursor mPhonesCursor;
+ private ListAdapter mPhonesAdapter;
+ private ArrayList<PhoneItem> mPhoneItemList;
public PhoneDisambigDialog(Context context, Cursor phonesCursor) {
this(context, phonesCursor, false /*make call*/);
@@ -57,6 +61,11 @@
mSendSms = sendSms;
mPhonesCursor = phonesCursor;
+ mPhoneItemList = makePhoneItemsList(phonesCursor);
+ Collapser.collapseList(mPhoneItemList);
+
+ mPhonesAdapter = new PhonesAdapter(mContext, mPhoneItemList);
+
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View setPrimaryView = inflater.
@@ -66,9 +75,10 @@
// Need to show disambig dialogue.
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext).
- setCursor(mPhonesCursor, this, Phone.NUMBER).
- setTitle(sendSms ? R.string.sms_disambig_title : R.string.call_disambig_title).
- setView(setPrimaryView);
+ setAdapter(mPhonesAdapter, this).
+ setTitle(sendSms ?
+ R.string.sms_disambig_title : R.string.call_disambig_title).
+ setView(setPrimaryView);
mDialog = dialogBuilder.create();
}
@@ -77,18 +87,25 @@
* Show the dialog.
*/
public void show() {
+ if (mPhoneItemList.size() == 1) {
+ // If there is only one after collapse, just select it, and close;
+ onClick(mDialog, 0);
+ return;
+ }
mDialog.show();
}
public void onClick(DialogInterface dialog, int which) {
- if (mPhonesCursor.moveToPosition(which)) {
- long id = mPhonesCursor.getLong(mPhonesCursor.getColumnIndex(Data._ID));
- String phone = mPhonesCursor.getString(mPhonesCursor.getColumnIndex(Phone.NUMBER));
+ if (mPhoneItemList.size() > which && which >= 0) {
+ PhoneItem phoneItem = mPhoneItemList.get(which);
+ long id = phoneItem.id;
+ String phone = phoneItem.phoneNumber;
+
if (mMakePrimary) {
ContentValues values = new ContentValues(1);
values.put(Data.IS_SUPER_PRIMARY, 1);
- mContext.getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, id),
- values, null, null);
+ mContext.getContentResolver().update(ContentUris.
+ withAppendedId(Data.CONTENT_URI, id), values, null, null);
}
if (mSendSms) {
@@ -108,4 +125,56 @@
public void onDismiss(DialogInterface dialog) {
mPhonesCursor.close();
}
+
+ private static class PhonesAdapter extends ArrayAdapter<PhoneItem> {
+
+ public PhonesAdapter(Context context, List<PhoneItem> objects) {
+ super(context, android.R.layout.simple_dropdown_item_1line,
+ android.R.id.text1, objects);
+ }
+ }
+
+ private class PhoneItem implements Collapsible<PhoneItem> {
+
+ String phoneNumber;
+ long id;
+
+ public PhoneItem(String newPhoneNumber, long newId) {
+ phoneNumber = newPhoneNumber;
+ id = newId;
+ }
+
+ public boolean collapseWith(PhoneItem phoneItem) {
+ if (!shouldCollapseWith(phoneItem)) {
+ return false;
+ }
+ // Just keep the number and id we already have.
+ return true;
+ }
+
+ public boolean shouldCollapseWith(PhoneItem phoneItem) {
+ if (PhoneNumberUtils.compare(PhoneDisambigDialog.this.mContext,
+ phoneNumber, phoneItem.phoneNumber)) {
+ return true;
+ }
+ return false;
+ }
+
+ public String toString() {
+ return phoneNumber;
+ }
+ }
+
+ private ArrayList<PhoneItem> makePhoneItemsList(Cursor phonesCursor) {
+ ArrayList<PhoneItem> phoneList = new ArrayList<PhoneItem>();
+
+ phonesCursor.moveToPosition(-1);
+ while (phonesCursor.moveToNext()) {
+ long id = phonesCursor.getLong(phonesCursor.getColumnIndex(Data._ID));
+ String phone = phonesCursor.getString(phonesCursor.getColumnIndex(Phone.NUMBER));
+ phoneList.add(new PhoneItem(phone, id));
+ }
+
+ return phoneList;
+ }
}
diff --git a/src/com/android/contacts/VCardExporter.java b/src/com/android/contacts/VCardExporter.java
deleted file mode 100644
index 5f13e99..0000000
--- a/src/com/android/contacts/VCardExporter.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Copyright (C) 2009 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.app.AlertDialog;
-import android.app.Dialog;
-import android.app.ProgressDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.pim.vcard.VCardComposer;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
-import java.util.HashSet;
-import java.util.Set;
-
-public class VCardExporter {
- private static final String LOG_TAG = "VCardExporter";
-
- // If true, VCardExporter is able to emits files longer than 8.3 format.
- private static final boolean ALLOW_LONG_FILE_NAME = false;
- private final String mTargetDirectory;
- private final String mFileNamePrefix;
- private final String mFileNameSuffix;
- private final int mFileIndexMinimum;
- private final int mFileIndexMaximum;
- private final String mFileNameExtension;
- private final String mVCardTypeStr;
- private final Set<String> mExtensionsToConsider;
-
- private ContactsListActivity mParentActivity;
- private ProgressDialog mProgressDialog;
-
- // Used temporaly when asking users to confirm the file name
- private String mTargetFileName;
-
- // String for storing error reason temporaly.
- private String mErrorReason;
-
- private ActualExportThread mActualExportThread;
-
- private class ConfirmListener implements DialogInterface.OnClickListener {
- private String mFileName;
-
- public ConfirmListener(String fileName) {
- mFileName = fileName;
- }
-
- public void onClick(DialogInterface dialog, int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- mActualExportThread = new ActualExportThread(mFileName);
- String title = getString(R.string.exporting_contact_list_title);
- String message = getString(R.string.exporting_contact_list_message, mFileName);
- mProgressDialog = new ProgressDialog(mParentActivity);
- mProgressDialog.setTitle(title);
- mProgressDialog.setMessage(message);
- mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- mProgressDialog.setOnCancelListener(mActualExportThread);
- mParentActivity.showDialog(R.id.dialog_exporting_vcard);
- mActualExportThread.start();
- }
- }
- }
-
- private class ActualExportThread extends Thread
- implements DialogInterface.OnCancelListener {
- private PowerManager.WakeLock mWakeLock;
- private String mFileName;
- private boolean mCanceled = false;
-
- public ActualExportThread(String fileName) {
- mFileName = fileName;
- PowerManager powerManager = (PowerManager)mParentActivity.getSystemService(
- Context.POWER_SERVICE);
- mWakeLock = powerManager.newWakeLock(
- PowerManager.SCREEN_DIM_WAKE_LOCK |
- PowerManager.ON_AFTER_RELEASE, LOG_TAG);
- }
-
- @Override
- public void run() {
- mWakeLock.acquire();
- VCardComposer composer = null;
- try {
- OutputStream outputStream = null;
- try {
- outputStream = new FileOutputStream(mFileName);
- } catch (FileNotFoundException e) {
- mErrorReason = getString(R.string.fail_reason_could_not_open_file,
- mFileName, e.getMessage());
- mParentActivity.showDialog(R.id.dialog_fail_to_export_with_reason);
- return;
- }
-
- composer = new VCardComposer(mParentActivity, mVCardTypeStr, true);
- // composer = new VCardComposer(mParentContext,
- // VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8, true);
- composer.addHandler(composer.new HandlerForOutputStream(outputStream));
-
- if (!composer.init()) {
- mErrorReason = getString(R.string.fail_reason_could_not_initialize_exporter,
- composer.getErrorReason());
- mParentActivity.showDialog(R.id.dialog_fail_to_export_with_reason);
- return;
- }
-
- int size = composer.getCount();
-
- mProgressDialog.setProgressNumberFormat(
- getString(R.string.exporting_contact_list_progress));
- mProgressDialog.setMax(size);
- mProgressDialog.setProgress(0);
-
- while (!composer.isAfterLast()) {
- if (mCanceled) {
- return;
- }
- if (!composer.createOneEntry()) {
- Log.e(LOG_TAG, "Failed to read a contact.");
- mErrorReason = getString(R.string.fail_reason_error_occurred_during_export,
- composer.getErrorReason());
- mParentActivity.showDialog(R.id.dialog_fail_to_export_with_reason);
- return;
- }
- mProgressDialog.incrementProgressBy(1);
- }
- } finally {
- if (composer != null) {
- composer.terminate();
- }
- mWakeLock.release();
- mProgressDialog.dismiss();
- mParentActivity.removeReferenceToVCardExporter();
- }
- }
-
- @Override
- public void finalize() {
- if (mWakeLock != null && mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
-
- public void cancel() {
- mCanceled = true;
- }
-
- public void onCancel(DialogInterface dialog) {
- cancel();
- }
- }
-
- /**
- * @param parentActivity must not be null
- */
- public VCardExporter(ContactsListActivity parentActivity) {
- mParentActivity = parentActivity;
- mTargetDirectory = getString(R.string.config_export_dir);
- mFileNamePrefix = getString(R.string.config_export_file_prefix);
- mFileNameSuffix = getString(R.string.config_export_file_suffix);
- mFileNameExtension = getString(R.string.config_export_file_extension);
- mVCardTypeStr = getString(R.string.config_export_vcard_type);
-
- mExtensionsToConsider = new HashSet<String>();
- mExtensionsToConsider.add(mFileNameExtension);
-
- final String additionalExtensions =
- getString(R.string.config_export_extensions_to_consider);
- if (!TextUtils.isEmpty(additionalExtensions)) {
- for (String extension : additionalExtensions.split(",")) {
- String trimed = extension.trim();
- if (trimed.length() > 0) {
- mExtensionsToConsider.add(trimed);
- }
- }
- }
-
- Resources resources = parentActivity.getResources();
- mFileIndexMinimum = resources.getInteger(R.integer.config_export_file_min_index);
- mFileIndexMaximum = resources.getInteger(R.integer.config_export_file_max_index);
- }
-
- /**
- * Tries to start exporting VCard. If there's no SDCard available,
- * an error dialog is shown.
- */
- public void startExportVCardToSdCard() {
- File targetDirectory = new File(mTargetDirectory);
-
- if (!(targetDirectory.exists() &&
- targetDirectory.isDirectory() &&
- targetDirectory.canRead()) &&
- !targetDirectory.mkdirs()) {
- mParentActivity.showDialog(R.id.dialog_sdcard_not_found);
- } else {
- mTargetFileName = getAppropriateFileName(mTargetDirectory);
- if (TextUtils.isEmpty(mTargetFileName)) {
- mTargetFileName = null;
- return;
- }
-
- mParentActivity.showDialog(R.id.dialog_confirm_export_vcard);
- }
- }
-
- /**
- * Tries to get an appropriate filename. Returns null if it fails.
- */
- private String getAppropriateFileName(final String destDirectory) {
- int fileNumberStringLength = 0;
- {
- // Calling Math.Log10() is costly.
- int tmp;
- for (fileNumberStringLength = 0, tmp = mFileIndexMaximum; tmp > 0;
- fileNumberStringLength++, tmp /= 10) {
- }
- }
- String bodyFormat = "%s%0" + fileNumberStringLength + "d%s";
-
- if (!ALLOW_LONG_FILE_NAME) {
- String possibleBody = String.format(bodyFormat,mFileNamePrefix, 1, mFileNameSuffix);
- if (possibleBody.length() > 8 || mFileNameExtension.length() > 3) {
- Log.e(LOG_TAG, "This code does not allow any long file name.");
- mErrorReason = getString(R.string.fail_reason_too_long_filename,
- String.format("%s.%s", possibleBody, mFileNameExtension));
- mParentActivity.showDialog(R.id.dialog_fail_to_export_with_reason);
- return null;
- }
- }
-
- // Note that this logic assumes that the target directory is case insensitive.
- // As of 2009-07-16, it is true since the external storage is only sdcard, and
- // it is formated as FAT/VFAT.
- // TODO: fix this.
- for (int i = mFileIndexMinimum; i <= mFileIndexMaximum; i++) {
- boolean numberIsAvailable = true;
- // SD Association's specification seems to require this feature, though we cannot
- // have the specification since it is proprietary...
- String body = null;
- for (String possibleExtension : mExtensionsToConsider) {
- body = String.format(bodyFormat, mFileNamePrefix, i, mFileNameSuffix);
- File file = new File(String.format("%s/%s.%s",
- destDirectory, body, possibleExtension));
- if (file.exists()) {
- numberIsAvailable = false;
- break;
- }
- }
- if (numberIsAvailable) {
- return String.format("%s/%s.%s", destDirectory, body, mFileNameExtension);
- }
- }
- mParentActivity.showDialog(R.string.fail_reason_too_many_vcard);
- return null;
- }
-
- public Dialog getExportConfirmationDialog() {
- if (TextUtils.isEmpty(mTargetFileName)) {
- Log.e(LOG_TAG, "Target file name is empty, which must not be!");
- // This situation is not acceptable (probably a bug!), but we don't have no reason to
- // show...
- mErrorReason = null;
- return getErrorDialogWithReason();
- }
-
- return new AlertDialog.Builder(mParentActivity)
- .setTitle(R.string.confirm_export_title)
- .setMessage(getString(R.string.confirm_export_message, mTargetFileName))
- .setPositiveButton(android.R.string.ok, new ConfirmListener(mTargetFileName))
- .setNegativeButton(android.R.string.cancel, null)
- .create();
- }
-
- public Dialog getExportingVCardDialog() {
- return mProgressDialog;
- }
-
- public Dialog getErrorDialogWithReason() {
- if (mErrorReason == null) {
- Log.e(LOG_TAG, "Error reason must have been set.");
- mErrorReason = getString(R.string.fail_reason_unknown);
- }
- return new AlertDialog.Builder(mParentActivity)
- .setTitle(R.string.exporting_contact_failed_title)
- .setMessage(getString(R.string.exporting_contact_failed_message, mErrorReason))
- .setPositiveButton(android.R.string.ok, null)
- .create();
- }
-
- public void cancelExport() {
- if (mActualExportThread != null) {
- mActualExportThread.cancel();
- mActualExportThread = null;
- }
- }
-
- private String getString(int resId, Object... formatArgs) {
- return mParentActivity.getString(resId, formatArgs);
- }
-
- private String getString(int resId) {
- return mParentActivity.getString(resId);
- }
-}
\ No newline at end of file
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index c1fab89..b1910d6 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -74,13 +74,11 @@
import android.view.Window;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
import android.view.animation.TranslateAnimation;
import android.view.animation.Animation.AnimationListener;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
@@ -137,6 +135,7 @@
protected ScrollingTabWidget mTabWidget;
protected ContactHeaderWidget mContactHeaderWidget;
protected View mBelowHeader;
+ protected TextView mAccountName;
protected View mBufferView;
private NotifyingAsyncQueryHandler mHandler;
@@ -218,6 +217,7 @@
mTabsVisible = false;
mBelowHeader = findViewById(R.id.below_header);
+ mAccountName = (TextView) findViewById(R.id.account_name);
mTabRawContactIdMap = new SparseArray<Long>();
@@ -608,6 +608,7 @@
}
ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
+ menu.setHeaderTitle(R.string.contactOptionsTitle);
if (entry.mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
menu.add(0, 0, 0, R.string.menu_call).setIntent(entry.intent);
menu.add(0, 0, 0, R.string.menu_sendSMS).setIntent(entry.secondaryIntent);
@@ -669,9 +670,13 @@
return true;
}
case R.id.menu_share: {
+ // TODO: Keep around actual LOOKUP_KEY, or formalize method of extracting
+ final String lookupKey = mLookupUri.getPathSegments().get(2);
+ final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
+
final Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType(Contacts.CONTENT_ITEM_TYPE);
- intent.putExtra(Intent.EXTRA_STREAM, mLookupUri);
+ intent.setType(Contacts.CONTENT_VCARD_TYPE);
+ intent.putExtra(Intent.EXTRA_STREAM, shareUri);
// Launch chooser to share contact via
final CharSequence chooseTitle = getText(R.string.share_via);
@@ -940,6 +945,14 @@
continue;
}
+ if (mTabsVisible) {
+ final String accountName = entValues.getAsString(RawContacts.ACCOUNT_NAME);
+ mAccountName.setText(getString(R.string.account_name_format, accountName));
+ mAccountName.setVisibility(View.VISIBLE);
+ } else {
+ mAccountName.setVisibility(View.GONE);
+ }
+
for (NamedContentValues subValue : entity.getSubValues()) {
ViewEntry entry = new ViewEntry();
@@ -1141,7 +1154,7 @@
/**
* A basic structure with the data for a contact entry in the list.
*/
- static class ViewEntry extends ContactEntryAdapter.Entry implements Collapsible<ViewEntry> {
+ class ViewEntry extends ContactEntryAdapter.Entry implements Collapsible<ViewEntry> {
public String resPackageName = null;
public int actionIcon = -1;
public boolean isPrimary = false;
@@ -1156,7 +1169,7 @@
public boolean collapseWith(ViewEntry entry) {
// assert equal collapse keys
- if (!getCollapseKey().equals(entry.getCollapseKey())) {
+ if (!shouldCollapseWith(entry)) {
return false;
}
@@ -1188,16 +1201,43 @@
return true;
}
- public String getCollapseKey() {
- StringBuilder hashSb = new StringBuilder();
- hashSb.append(data);
- hashSb.append(mimetype);
- hashSb.append((intent != null && intent.getAction() != null)
- ? intent.getAction() : "");
- hashSb.append((secondaryIntent != null && secondaryIntent.getAction() != null)
- ? secondaryIntent.getAction() : "");
- hashSb.append(actionIcon);
- return hashSb.toString();
+ public boolean shouldCollapseWith(ViewEntry entry) {
+ if (entry == null) {
+ return false;
+ }
+
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)
+ && Phone.CONTENT_ITEM_TYPE.equals(entry.mimetype)) {
+ if (!PhoneNumberUtils.compare(ViewContactActivity.this, data, entry.data)) {
+ return false;
+ }
+ } else {
+ if (!equals(data, entry.data)) {
+ return false;
+ }
+ }
+
+ if (!equals(mimetype, entry.mimetype)
+ || !intentCollapsible(intent, entry.intent)
+ || !intentCollapsible(secondaryIntent, entry.secondaryIntent)
+ || actionIcon != entry.actionIcon) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean equals(Object a, Object b) {
+ return a==b || (a != null && a.equals(b));
+ }
+
+ private boolean intentCollapsible(Intent a, Intent b) {
+ if (a == b) {
+ return true;
+ } else if ((a != null && b != null) && equals(a.getAction(), b.getAction())) {
+ return true;
+ }
+ return false;
}
}
diff --git a/src/com/android/contacts/model/Editor.java b/src/com/android/contacts/model/Editor.java
index d6f7003..b7ae045 100644
--- a/src/com/android/contacts/model/Editor.java
+++ b/src/com/android/contacts/model/Editor.java
@@ -49,7 +49,7 @@
* builds any needed views. Any changes performed by the user will be
* written back to that same object.
*/
- public void setValues(DataKind kind, ValuesDelta values, EntityDelta state);
+ public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly);
/**
* Add a specific {@link EditorListener} to this {@link Editor}.
diff --git a/src/com/android/contacts/model/EntityDelta.java b/src/com/android/contacts/model/EntityDelta.java
index f95bc12..7ac0f5b 100644
--- a/src/com/android/contacts/model/EntityDelta.java
+++ b/src/com/android/contacts/model/EntityDelta.java
@@ -437,6 +437,7 @@
protected ContentValues mBefore;
protected ContentValues mAfter;
protected String mIdColumn = BaseColumns._ID;
+ private boolean mFromTemplate;
/**
* Next value to assign to {@link #mIdColumn} when building an insert
@@ -543,6 +544,14 @@
return isPrimary == null ? false : isPrimary != 0;
}
+ public void setFromTemplate(boolean isFromTemplate) {
+ mFromTemplate = isFromTemplate;
+ }
+
+ public boolean isFromTemplate() {
+ return mFromTemplate;
+ }
+
public boolean beforeExists() {
return (mBefore != null && mBefore.containsKey(mIdColumn));
}
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index beaf3e6..d6f6571 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -389,7 +389,7 @@
// TODO: remove this verbose logging
Log.w(TAG, "Trimming: " + entry.toString());
entry.markDeleted();
- } else {
+ } else if (!entry.isFromTemplate()) {
hasValues = true;
}
}
diff --git a/src/com/android/contacts/model/ExchangeSource.java b/src/com/android/contacts/model/ExchangeSource.java
index 0a7fb23..6d4e357 100644
--- a/src/com/android/contacts/model/ExchangeSource.java
+++ b/src/com/android/contacts/model/ExchangeSource.java
@@ -125,8 +125,6 @@
.add(buildPhoneType(Phone.TYPE_RADIO).setSecondary(true).setSpecificMax(1));
kind.typeList.add(buildPhoneType(Phone.TYPE_ASSISTANT).setSecondary(true)
.setSpecificMax(1).setCustomColumn(Phone.LABEL));
- kind.typeList.add(buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true)
- .setSpecificMax(1).setCustomColumn(Phone.LABEL));
kind.fieldList = Lists.newArrayList();
kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
diff --git a/src/com/android/contacts/model/GoogleSource.java b/src/com/android/contacts/model/GoogleSource.java
index 9968ace..b6f1e29 100644
--- a/src/com/android/contacts/model/GoogleSource.java
+++ b/src/com/android/contacts/model/GoogleSource.java
@@ -42,7 +42,6 @@
public class GoogleSource extends FallbackSource {
public static final String ACCOUNT_TYPE = "com.google.GAIA";
-
public GoogleSource(String resPackageName) {
this.accountType = ACCOUNT_TYPE;
this.resPackageName = null;
@@ -160,6 +159,7 @@
public static final void attemptMyContactsMembership(EntityDelta state, Context context) {
final ValuesDelta stateValues = state.getValues();
+ stateValues.setFromTemplate(true);
final String accountName = stateValues.getAsString(RawContacts.ACCOUNT_NAME);
final String accountType = stateValues.getAsString(RawContacts.ACCOUNT_TYPE);
attemptMyContactsMembership(state, accountName, accountType, context, true);
diff --git a/src/com/android/contacts/model/Sources.java b/src/com/android/contacts/model/Sources.java
index 04fa493..7d06cab 100644
--- a/src/com/android/contacts/model/Sources.java
+++ b/src/com/android/contacts/model/Sources.java
@@ -24,6 +24,7 @@
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorDescription;
+import android.accounts.OnAccountsUpdatedListener;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -46,10 +47,11 @@
* Singleton holder for all parsed {@link ContactsSource} available on the
* system, typically filled through {@link PackageManager} queries.
*/
-public class Sources extends BroadcastReceiver {
+public class Sources extends BroadcastReceiver implements OnAccountsUpdatedListener {
private static final String TAG = "Sources";
- private Context mContext;
+ private Context mApplicationContext;
+ private AccountManager mAccountManager;
private ContactsSource mFallbackSource = null;
@@ -76,13 +78,22 @@
* Internal constructor that only performs initial parsing.
*/
private Sources(Context context) {
- mContext = context;
+ mApplicationContext = context.getApplicationContext();
+ mAccountManager = AccountManager.get(mApplicationContext);
// Create fallback contacts source for on-phone contacts
mFallbackSource = new FallbackSource();
- loadAccounts();
- registerIntentReceivers(context);
+ queryAccounts();
+
+ // Request updates when packages or accounts change
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addDataScheme("package");
+
+ mApplicationContext.registerReceiver(this, filter);
+ mAccountManager.addOnAccountsUpdatedListener(this, null, false);
}
/** @hide exposed for unit tests */
@@ -97,49 +108,50 @@
mKnownPackages.add(source.resPackageName);
}
- private void registerIntentReceivers(Context context) {
- IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addDataScheme("package");
-
- // We use getApplicationContext() so that the broadcast reciever can stay registered for
- // the length of the application lifetime (instead of the calling activity's lifetime).
- // This is so that we can notified of package changes, and purge the cache accordingly,
- // but not be woken up if the application process isn't already running, since we will
- // have no cache to clear at that point.
- context.getApplicationContext().registerReceiver(this, filter);
- }
-
/** {@inheritDoc} */
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
final String packageName = intent.getData().getSchemeSpecificPart();
- final boolean matchingPackage = mKnownPackages.contains(packageName);
- final boolean validAction = Intent.ACTION_PACKAGE_REMOVED.equals(action)
+ if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
|| Intent.ACTION_PACKAGE_ADDED.equals(action)
- || Intent.ACTION_PACKAGE_CHANGED.equals(action);
-
- if (matchingPackage && validAction) {
- for (ContactsSource source : mSources.values()) {
- if (TextUtils.equals(packageName, source.resPackageName)) {
- // Invalidate any cache for the changed package
- source.invalidateCache();
- }
+ || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ final boolean knownPackage = mKnownPackages.contains(packageName);
+ if (knownPackage) {
+ // Invalidate cache of existing source
+ invalidateCache(packageName);
+ } else {
+ // Unknown source, so reload from scratch
+ queryAccounts();
}
}
}
+ protected void invalidateCache(String packageName) {
+ for (ContactsSource source : mSources.values()) {
+ if (TextUtils.equals(packageName, source.resPackageName)) {
+ // Invalidate any cache for the changed package
+ source.invalidateCache();
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onAccountsUpdated(Account[] accounts) {
+ // Refresh to catch any changed accounts
+ queryAccounts();
+ }
+
/**
* Blocking call to load all {@link AuthenticatorDescription} known by the
* {@link AccountManager} on the system.
*/
- protected void loadAccounts() {
+ protected synchronized void queryAccounts() {
mSources.clear();
+ mKnownPackages.clear();
- final AccountManager am = AccountManager.get(mContext);
+ final AccountManager am = mAccountManager;
final IContentService cs = ContentResolver.getContentService();
try {
@@ -200,7 +212,7 @@
* returned may require inflation before they can be used.
*/
public ArrayList<Account> getAccounts(boolean writableOnly) {
- final AccountManager am = AccountManager.get(mContext);
+ final AccountManager am = mAccountManager;
final Account[] accounts = am.getAccounts();
final ArrayList<Account> matching = Lists.newArrayList();
@@ -259,7 +271,7 @@
return source;
} else {
// Not inflated, but requested that we force-inflate
- source.ensureInflated(mContext, inflateLevel);
+ source.ensureInflated(mApplicationContext, inflateLevel);
return source;
}
}
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index 51b10c5..14f80b9 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -97,6 +97,7 @@
private ScrollingTabWidget mTabWidget;
private ContactHeaderWidget mHeader;
+ private TextView mAccountName;
private ContactEditorView mEditor;
@@ -120,11 +121,12 @@
// Header bar is filled later after queries finish
mHeader = (ContactHeaderWidget)this.findViewById(R.id.contact_header_widget);
mHeader.setContactHeaderListener(this);
- mHeader.showStar(true);
+ mHeader.showStar(false);
mHeader.enableClickListeners();
mTabWidget = (ScrollingTabWidget)this.findViewById(R.id.tab_widget);
mTabWidget.setTabSelectionListener(this);
+ mAccountName = (TextView)this.findViewById(R.id.account_name);
// Build editor and listen for photo requests
mEditor = (ContactEditorView)this.findViewById(android.R.id.tabcontent);
@@ -394,10 +396,15 @@
if (entity == null) return;
final Sources sources = Sources.getInstance(this);
- final String accountType = entity.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+ final ValuesDelta values = entity.getValues();
+ final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
+ final String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
final ContactsSource source = sources.getInflatedSource(accountType,
ContactsSource.LEVEL_CONSTRAINTS);
+ mAccountName.setText(getString(R.string.account_name_format, accountName));
+ mAccountName.setVisibility(View.VISIBLE);
+
// Assign editor state based on entity and source
mEditor.setState(entity, source);
}
diff --git a/src/com/android/contacts/ui/widget/ContactEditorView.java b/src/com/android/contacts/ui/widget/ContactEditorView.java
index 35a32cf..cd94e52 100644
--- a/src/com/android/contacts/ui/widget/ContactEditorView.java
+++ b/src/com/android/contacts/ui/widget/ContactEditorView.java
@@ -173,6 +173,8 @@
EntityModifier.ensureKindExists(state, source, Photo.CONTENT_ITEM_TYPE);
mHasPhotoEditor = (source.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null);
mPhoto.setVisibility(mHasPhotoEditor ? View.VISIBLE : View.GONE);
+ mPhoto.setEnabled(!source.readOnly);
+ mName.setEnabled(!source.readOnly);
mReadOnly.setVisibility(source.readOnly ? View.VISIBLE : View.GONE);
@@ -185,18 +187,18 @@
if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
// Handle special case editor for structured name
final ValuesDelta primary = state.getPrimaryEntry(mimeType);
- mName.setValues(kind, primary, state);
+ mName.setValues(kind, primary, state, source.readOnly);
} else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
// Handle special case editor for photos
final ValuesDelta primary = state.getPrimaryEntry(mimeType);
- mPhoto.setValues(kind, primary, state);
+ mPhoto.setValues(kind, primary, state, source.readOnly);
} else {
// Otherwise use generic section-based editors
if (kind.fieldList == null) continue;
final ViewGroup parent = kind.secondary ? mSecondary : mGeneral;
final KindSectionView section = (KindSectionView)mInflater.inflate(
R.layout.item_kind_section, parent, false);
- section.setState(kind, state);
+ section.setState(kind, state, source.readOnly);
section.setId(kind.weight);
parent.addView(section);
}
diff --git a/src/com/android/contacts/ui/widget/GenericEditorView.java b/src/com/android/contacts/ui/widget/GenericEditorView.java
index 53dd2e0..6387374 100644
--- a/src/com/android/contacts/ui/widget/GenericEditorView.java
+++ b/src/com/android/contacts/ui/widget/GenericEditorView.java
@@ -70,6 +70,7 @@
protected DataKind mKind;
protected ValuesDelta mEntry;
protected EntityDelta mState;
+ protected boolean mReadOnly;
protected boolean mHideOptional = true;
@@ -113,6 +114,16 @@
mDelete.setVisibility(deletable ? View.VISIBLE : View.INVISIBLE);
}
+ public void setEnabled(boolean enabled) {
+ mLabel.setEnabled(enabled);
+ final int count = mFields.getChildCount();
+ for (int pos = 0; pos < count; pos++) {
+ final View v = mFields.getChildAt(pos);
+ v.setEnabled(enabled);
+ }
+ mMore.setEnabled(enabled);
+ }
+
/**
* Build the current label state based on selected {@link EditType} and
* possible custom label string.
@@ -144,17 +155,20 @@
}
private void rebuildValues() {
- setValues(mKind, mEntry, mState);
+ setValues(mKind, mEntry, mState, mReadOnly);
}
/**
* Prepare this editor using the given {@link DataKind} for defining
* structure and {@link ValuesDelta} describing the content to edit.
*/
- public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state) {
+ public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly) {
mKind = kind;
mEntry = entry;
mState = state;
+ mReadOnly = readOnly;
+
+ final boolean enabled = !readOnly;
if (!entry.isVisible()) {
// Hide ourselves entirely if deleted
@@ -167,6 +181,7 @@
// Display label selector if multiple types available
final boolean hasTypes = EntityModifier.hasEditTypes(kind);
mLabel.setVisibility(hasTypes ? View.VISIBLE : View.GONE);
+ mLabel.setEnabled(enabled);
if (hasTypes) {
mType = EntityModifier.getCurrentType(entry, kind);
rebuildLabel();
@@ -207,6 +222,7 @@
final boolean couldHide = (TextUtils.isEmpty(value) && field.optional);
final boolean willHide = (mHideOptional && couldHide);
fieldView.setVisibility(willHide ? View.GONE : View.VISIBLE);
+ fieldView.setEnabled(enabled);
hidePossible = hidePossible || couldHide;
mFields.addView(fieldView);
@@ -214,6 +230,7 @@
// When hiding fields, place expandable
mMore.setVisibility(hidePossible ? View.VISIBLE : View.GONE);
+ mMore.setEnabled(enabled);
}
/**
diff --git a/src/com/android/contacts/ui/widget/KindSectionView.java b/src/com/android/contacts/ui/widget/KindSectionView.java
index 5a63992..b52cfd0 100644
--- a/src/com/android/contacts/ui/widget/KindSectionView.java
+++ b/src/com/android/contacts/ui/widget/KindSectionView.java
@@ -50,6 +50,7 @@
private DataKind mKind;
private EntityDelta mState;
+ private boolean mReadOnly;
public KindSectionView(Context context) {
super(context);
@@ -87,9 +88,10 @@
// Ignore requests
}
- public void setState(DataKind kind, EntityDelta state) {
+ public void setState(DataKind kind, EntityDelta state, boolean readOnly) {
mKind = kind;
mState = state;
+ mReadOnly = readOnly;
// TODO: handle resources from remote packages
mTitle.setText(kind.titleRes);
@@ -114,7 +116,7 @@
final GenericEditorView editor = (GenericEditorView)mInflater.inflate(
R.layout.item_generic_editor, mEditors, false);
- editor.setValues(mKind, entry, mState);
+ editor.setValues(mKind, entry, mState, mReadOnly);
editor.setEditorListener(this);
editor.setId(entry.getViewId());
mEditors.addView(editor);
@@ -129,7 +131,8 @@
protected void updateAddEnabled() {
// Set enabled state on the "add" view
final boolean canInsert = EntityModifier.canInsert(mState, mKind);
- mAdd.setEnabled(canInsert);
+ final boolean isEnabled = !mReadOnly && canInsert;
+ mAdd.setEnabled(isEnabled);
}
/** {@inheritDoc} */
diff --git a/src/com/android/contacts/ui/widget/PhotoEditorView.java b/src/com/android/contacts/ui/widget/PhotoEditorView.java
index cde314d..184b907 100644
--- a/src/com/android/contacts/ui/widget/PhotoEditorView.java
+++ b/src/com/android/contacts/ui/widget/PhotoEditorView.java
@@ -74,7 +74,7 @@
}
/** {@inheritDoc} */
- public void setValues(DataKind kind, ValuesDelta values, EntityDelta state) {
+ public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly) {
mEntry = values;
if (values != null) {
// Try decoding photo if actual entry
@@ -85,6 +85,7 @@
setScaleType(ImageView.ScaleType.CENTER_CROP);
setImageBitmap(photo);
+ setEnabled(!readOnly);
mHasSetPhoto = true;
} else {
resetDefault();
diff --git a/src/com/android/contacts/util/AccountSelectionUtil.java b/src/com/android/contacts/util/AccountSelectionUtil.java
new file mode 100644
index 0000000..cf83581
--- /dev/null
+++ b/src/com/android/contacts/util/AccountSelectionUtil.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2009 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 com.android.contacts.ImportVCardActivity;
+import com.android.contacts.R;
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.GoogleSource;
+import com.android.contacts.model.Sources;
+
+import android.accounts.Account;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * Utility class for selectiong an Account for importing contact(s)
+ */
+public class AccountSelectionUtil {
+ // TODO: maybe useful for EditContactActivity.java...
+ private static final String LOG_TAG = "AccountSelectionUtil";
+
+ private static class AccountSelectedListener
+ implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+
+ final private Context mContext;
+ final private List<Account> mAccountList;
+ final private int mResId;
+
+ public AccountSelectedListener(Context context, List<Account> accountList, int resId) {
+ if (accountList == null || accountList.size() == 0) {
+ Log.e(LOG_TAG, "The size of Account list is 0.");
+ }
+ mContext = context;
+ mAccountList = accountList;
+ mResId = resId;
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ doImport(mContext, mResId, mAccountList.get(which));
+ }
+
+ public void onCancel(DialogInterface dialog) {
+ dialog.dismiss();
+ }
+ }
+
+ public static Dialog getSelectAccountDialog(Context context, int resId) {
+ return getSelectAccountDialog(context, resId, null);
+ }
+
+ public static Dialog getSelectAccountDialog(Context context, int resId,
+ DialogInterface.OnCancelListener onCancelListener) {
+ final Sources sources = Sources.getInstance(context);
+ final List<Account> writableAccountList = sources.getAccounts(true);
+
+ // Assume accountList.size() > 1
+
+ // Wrap our context to inflate list items using correct theme
+ final Context dialogContext = new ContextThemeWrapper(
+ context, android.R.style.Theme_Light);
+ final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ final ArrayAdapter<Account> accountAdapter =
+ new ArrayAdapter<Account>(context, android.R.layout.simple_list_item_2,
+ writableAccountList) {
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = dialogInflater.inflate(
+ android.R.layout.simple_list_item_2,
+ parent, false);
+ }
+
+ // TODO: show icon along with title
+ final TextView text1 =
+ (TextView)convertView.findViewById(android.R.id.text1);
+ final TextView text2 =
+ (TextView)convertView.findViewById(android.R.id.text2);
+
+ final Account account = this.getItem(position);
+ final ContactsSource source =
+ sources.getInflatedSource(account.type,
+ ContactsSource.LEVEL_SUMMARY);
+ final Context context = getContext();
+
+ text1.setText(account.name);
+ text2.setText(source.getDisplayLabel(context));
+
+ return convertView;
+ }
+ };
+
+ AccountSelectedListener accountSelectedListener =
+ new AccountSelectedListener(context, writableAccountList, resId);
+ return new AlertDialog.Builder(context)
+ .setTitle(R.string.dialog_new_contact_account)
+ .setSingleChoiceItems(accountAdapter, 0, accountSelectedListener)
+ .setOnCancelListener(accountSelectedListener)
+ .create();
+ }
+
+ public static void doImport(Context context, int resId, Account account) {
+ switch (resId) {
+ case R.string.import_from_sim: {
+ doImportFromSim(context, account);
+ break;
+ }
+ case R.string.import_from_sdcard: {
+ doImportFromSdCard(context, account);
+ break;
+ }
+ }
+ }
+
+ public static void doImportFromSim(Context context, Account account) {
+ if (account != null) {
+ GoogleSource.createMyContactsIfNotExist(account, context);
+ }
+
+ Intent importIntent = new Intent(Intent.ACTION_VIEW);
+ importIntent.setType("vnd.android.cursor.item/sim-contact");
+ if (account != null) {
+ importIntent.putExtra("account_name", account.name);
+ importIntent.putExtra("account_type", account.type);
+ }
+ importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts");
+ context.startActivity(importIntent);
+ }
+
+ public static void doImportFromSdCard(Context context, Account account) {
+ if (account != null) {
+ GoogleSource.createMyContactsIfNotExist(account, context);
+ }
+
+ Intent importIntent = new Intent(context, ImportVCardActivity.class);
+ if (account != null) {
+ importIntent.putExtra("account_name", account.name);
+ importIntent.putExtra("account_type", account.type);
+ }
+ context.startActivity(importIntent);
+ }
+}