Merge "Fixing direct dial shortcut maker"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 307b6cc..24dfe86 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -400,7 +400,8 @@
<!-- Edit and insert details for a contact -->
<activity
android:name=".activities.ContactEditorActivity"
- android:theme="@style/TallTitleBarTheme">
+ android:theme="@style/TallTitleBarTheme"
+ android:windowSoftInputMode="adjustResize">
<intent-filter android:label="@string/editContactDescription">
<action android:name="android.intent.action.EDIT" />
@@ -525,6 +526,7 @@
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="text/directory" />
+ <data android:mimeType="text/vcard" />
<data android:mimeType="text/x-vcard" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
@@ -533,13 +535,13 @@
<activity android:name=".vcard.SelectAccountActivity"
android:theme="@style/BackgroundOnly" />
- <service
- android:name=".vcard.ImportVCardService"
- android:exported="false" />
-
<activity android:name=".vcard.ExportVCardActivity"
android:theme="@style/BackgroundOnly" />
+ <service
+ android:name=".vcard.VCardService"
+ android:exported="false" />
+
<!-- Pinned header list demo -->
<activity android:name=".widget.PinnedHeaderListDemoActivity">
<intent-filter>
diff --git a/res/layout/contact_editor_fragment.xml b/res/layout/contact_editor_fragment.xml
index 3324d35..0055956 100644
--- a/res/layout/contact_editor_fragment.xml
+++ b/res/layout/contact_editor_fragment.xml
@@ -25,11 +25,15 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
- <ListView android:id="@android:id/list"
- android:layout_width="match_parent"
- android:layout_height="0px"
- android:layout_weight="1"
- android:background="@drawable/title_bar_shadow"
- />
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1">
+ <com.android.contacts.views.editor.MyListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/title_bar_shadow"
+ />
+ </ScrollView>
</LinearLayout>
diff --git a/res/layout/list_edit_item_field_and_type.xml b/res/layout/list_edit_item_field_and_type.xml
new file mode 100644
index 0000000..ac43ceb
--- /dev/null
+++ b/res/layout/list_edit_item_field_and_type.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 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.
+ */
+-->
+
+<com.android.contacts.views.editor.view.FieldAndTypeView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:orientation="horizontal"
+ android:paddingLeft="9dip"
+ android:gravity="center_vertical"
+ android:background="@drawable/edit_rawcontact_bg"
+>
+
+ <TextView android:id="@+id/caption"
+ android:layout_width="60dip"
+ android:layout_height="wrap_content"
+ />
+
+ <EditText android:id="@+id/field"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ />
+
+ <Button android:id="@+id/type"
+ android:layout_width="100dip"
+ android:layout_height="wrap_content"
+ />
+
+</com.android.contacts.views.editor.view.FieldAndTypeView>
diff --git a/res/layout/list_edit_item_footer.xml b/res/layout/list_edit_item_footer.xml
index cf53ceb..4ad3638 100644
--- a/res/layout/list_edit_item_footer.xml
+++ b/res/layout/list_edit_item_footer.xml
@@ -17,7 +17,7 @@
*/
-->
-<com.android.contacts.views.editor.typeViews.FooterView
+<com.android.contacts.views.editor.view.FooterView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -46,4 +46,4 @@
android:text="@string/edit_delete_rawcontact"
/>
-</com.android.contacts.views.editor.typeViews.FooterView>
+</com.android.contacts.views.editor.view.FooterView>
diff --git a/res/layout/list_edit_item_header.xml b/res/layout/list_edit_item_header.xml
index d2f9317..aa33a78 100644
--- a/res/layout/list_edit_item_header.xml
+++ b/res/layout/list_edit_item_header.xml
@@ -17,7 +17,7 @@
*/
-->
-<com.android.contacts.views.editor.typeViews.HeaderView
+<com.android.contacts.views.editor.view.HeaderView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -42,4 +42,4 @@
android:scaleType="center"
android:background="@android:drawable/list_selector_background"
/>
-</com.android.contacts.views.editor.typeViews.HeaderView>
+</com.android.contacts.views.editor.view.HeaderView>
diff --git a/res/layout/list_edit_item_photo.xml b/res/layout/list_edit_item_photo.xml
index c8f0cdf..9e7951f 100644
--- a/res/layout/list_edit_item_photo.xml
+++ b/res/layout/list_edit_item_photo.xml
@@ -17,7 +17,7 @@
*/
-->
-<com.android.contacts.views.editor.typeViews.PhotoView
+<com.android.contacts.views.editor.view.PhotoView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -69,4 +69,4 @@
android:scaleType="center"
android:background="@android:drawable/list_selector_background"
/>
-</com.android.contacts.views.editor.typeViews.PhotoView>
+</com.android.contacts.views.editor.view.PhotoView>
diff --git a/res/layout/list_edit_item_text_icons.xml b/res/layout/list_edit_item_text_icons.xml
index ab7b149..03868b7 100644
--- a/res/layout/list_edit_item_text_icons.xml
+++ b/res/layout/list_edit_item_text_icons.xml
@@ -17,7 +17,7 @@
*/
-->
-<com.android.contacts.views.editor.typeViews.DataView
+<com.android.contacts.views.editor.view.DataView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -96,4 +96,4 @@
android:background="@android:drawable/list_selector_background"
/>
-</com.android.contacts.views.editor.typeViews.DataView>
+</com.android.contacts.views.editor.view.DataView>
diff --git a/res/layout/two_pane_activity.xml b/res/layout/two_pane_activity.xml
index bcf0ad8..407360b 100644
--- a/res/layout/two_pane_activity.xml
+++ b/res/layout/two_pane_activity.xml
@@ -24,21 +24,22 @@
layout="@layout/search_bar"/>
<LinearLayout
- android:id="@+id/two_pane_activity"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.android.contacts.list.DefaultContactBrowseListFragment"
android:id="@+id/two_pane_list"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
android:layout_weight="1" />
- <fragment android:name="com.android.contacts.views.detail.ContactDetailFragment"
- android:id="@+id/two_pane_detail"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1" />
+ <!-- Holder for detail- or editor-fragment. -->
+ <LinearLayout
+ android:id="@+id/two_pane_right_view"
+ android:orientation="horizontal"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 9da8428..791d23e 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -162,7 +162,7 @@
<string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Non sono presenti contatti da visualizzare (se hai appena aggiunto un account, la sincronizzazione dei contatti potrebbe richiedere alcuni minuti)."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e tocca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Account"</b></font>" per aggiungere o configurare un account con contatti sincronizzabili con il telefono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opzioni di visualizzazione"</b></font>" per modificare i contatti visibili"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare da zero un nuovo contatto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa/esporta"</b></font>\n</li></string>
<string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Non sono presenti contatti da visualizzare."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e tocca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Account"</b></font>" per aggiungere o configurare un account con contatti sincronizzabili con il telefono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare da zero un nuovo contatto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa/esporta"</b></font>\n</li></string>
<string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Non sono presenti contatti da visualizzare (se hai appena aggiunto un account, la sincronizzazione dei contatti potrebbe richiedere alcuni minuti)."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e tocca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Account"</b></font>" per aggiungere o configurare un account con contatti sincronizzabili con il telefono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opzioni di visualizzazione"</b></font>" per modificare i contatti visibili"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare da zero un nuovo contatto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa/esporta"</b></font>\n</li></string>
- <string name="noFavoritesHelpText" msgid="3744655776704833277">"Non sono presenti preferiti."\n\n"Per aggiungere un contatto al tuo elenco di preferiti:"\n\n" "<li>"Tocca la scheda "<b>"Contatti."</b>\n</li>" "\n<li>"Tocca il contatto da aggiungere ai preferiti."\n</li>" "\n<li>"Tocca la stella accanto al nome del contatto."\n</li></string>
+ <string name="noFavoritesHelpText" msgid="3744655776704833277">"Non sono presenti preferiti."\n\n"Per aggiungere un contatto al tuo elenco di preferiti:"\n\n<li>"Tocca la scheda "<b>"Contatti."</b>\n</li>" "\n<li>"Tocca il contatto da aggiungere ai preferiti."\n</li>" "\n<li>"Tocca la stella accanto al nome del contatto."\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Tutti i contatti"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Speciali"</string>
<string name="liveFolder_phones_label" msgid="1709786878793436245">"Telefoni"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 7cc424e..a9802b8 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -250,14 +250,14 @@
<string name="call_disambig_title" msgid="1911302597959335178">"전화 걸기:"</string>
<string name="sms_disambig_title" msgid="4675399294513152364">"다음을 사용하여 문자 보내기:"</string>
<string name="make_primary" msgid="5829291915305113983">"이 선택사항 저장"</string>
- <string name="quickcontact_missing_app" msgid="4600366393134289038">"이 작업을 처리하는 응용프로그램을 찾을 수 없습니다."</string>
+ <string name="quickcontact_missing_app" msgid="4600366393134289038">"이 작업을 처리하는 애플리케이션을 찾을 수 없습니다."</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"이 선택사항 저장"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"알 수 없음"</string>
<string name="menu_accounts" msgid="8499114602017077970">"계정"</string>
<string name="menu_import_export" msgid="3765725645491577190">"가져오기/내보내기"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"연락처 가져오기/내보내기"</string>
<string name="menu_share" msgid="943789700636542260">"공유"</string>
- <string name="share_via" msgid="563121028023030093">"연락처 공유에 사용할 응용프로그램:"</string>
+ <string name="share_via" msgid="563121028023030093">"연락처 공유에 사용할 애플리케이션:"</string>
<string name="share_error" msgid="4374508848981697170">"연락처를 공유할 수 없습니다."</string>
<string name="nameLabelsGroup" msgid="2034640839640477827">"이름"</string>
<string name="nicknameLabelsGroup" msgid="2891682101053358010">"닉네임"</string>
@@ -374,7 +374,7 @@
<string name="locale_change_in_progress" msgid="1124266507671178413">"변경된 언어를 반영하도록 연락처 목록을 업데이트하는 중입니다."\n\n"잠시 기다려 주세요."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"연락처 목록을 업데이트하고 있습니다."\n\n"잠시 기다려 주세요..."</string>
<string name="upgrade_out_of_memory" msgid="492151063059824962">"연락처를 업그레이드하는 중입니다. "\n\n"업그레이드 프로세스에는 약 <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g>Mb의 휴대전화 내부 저장공간이 필요합니다."\n\n"다음 옵션 중 하나를 선택하세요."</string>
- <string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"일부 응용프로그램 제거"</string>
+ <string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"일부 애플리케이션 제거"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"업그레이드 다시 시도"</string>
<string name="search_results_for" msgid="8705490885073188513">"<xliff:g id="QUERY">%s</xliff:g>에 대한 검색결과"</string>
<string name="search_results_searching" msgid="7755623475227227314">"검색 중..."</string>
diff --git a/res/values/donottranslate_config.xml b/res/values/donottranslate_config.xml
index f1c6951..cad2a63 100644
--- a/res/values/donottranslate_config.xml
+++ b/res/values/donottranslate_config.xml
@@ -53,7 +53,7 @@
<string name="config_export_vcard_type" translatable="false">default</string>
<!-- Directory in which exported VCard file is stored -->
- <string name="config_export_dir" translatable="false">/sdcard</string>
+ <string name="config_export_dir" translatable="false">/mnt/sdcard</string>
<!-- Prefix of exported VCard file -->
<string name="config_export_file_prefix" translatable="false"></string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 29a39df..1b07bff 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -748,7 +748,7 @@
data storage. -->
<string name="caching_vcard_message">Importer is caching vCard(s) to local temporary storage. Actual import will start soon.</string>
- <!-- The message shown while reading vCard(s).
+ <!-- The message shown while importing vCard(s).
First argument is current index of contacts to be imported.
Second argument is the total number of contacts.
Third argument is the Uri which is being read. -->
@@ -764,7 +764,7 @@
<string name="reading_vcard_canceled_title">Reading vCard data was canceled</string>
<!-- The title shown when reading vCard is canceled (probably by a user) -->
- <string name="reading_vcard_finished_title">Finished Reading vCard data</string>
+ <string name="importing_vcard_finished_title">Finished importing vCard</string>
<!-- The message shown when vCard importer started running. -->
<string name="vcard_importer_start_message">vCard importer started.</string>
@@ -772,7 +772,7 @@
<!-- The message shown when additional vCard to be imported is given during the import for others -->
<string name="vcard_importer_will_start_message">vCard importer will import the vCard after a while.</string>
- <!-- The percentage, used for expressing the progress of vCard import. -->
+ <!-- The percentage, used for expressing the progress of vCard import/export. -->
<string name="percentage">%s%%</string>
<!-- Dialog title shown when a user confirms whether he/she export Contact data -->
@@ -801,6 +801,12 @@
mention it here. -->
<string name="fail_reason_too_long_filename">Required filename is too long (\"<xliff:g id="filename">%s</xliff:g>\")</string>
+ <!-- The message shown when vCard importer started running. -->
+ <string name="vcard_exporter_start_message">vCard exporter started.</string>
+
+ <!-- The title shown when reading vCard is canceled (probably by a user) -->
+ <string name="exporting_vcard_finished_title">Finished exporting vCard</string>
+
<!-- Dialog title shown when the application is exporting contact data outside -->
<string name="exporting_contact_list_title">Exporting contact data</string>
@@ -1230,5 +1236,5 @@
<string name="edit_delete_rawcontact">Delete</string>
<!-- Shown in a Toast to indicate an error while trying to save the Data -->
- <string name="edit_error_saving"></string>
+ <string name="edit_error_saving">Error saving</string>
</resources>
diff --git a/src/com/android/contacts/activities/TwoPaneActivity.java b/src/com/android/contacts/activities/TwoPaneActivity.java
index ec6d4e3..63283ad 100644
--- a/src/com/android/contacts/activities/TwoPaneActivity.java
+++ b/src/com/android/contacts/activities/TwoPaneActivity.java
@@ -20,6 +20,7 @@
import com.android.contacts.list.DefaultContactBrowseListFragment;
import com.android.contacts.list.OnContactBrowserActionListener;
import com.android.contacts.views.detail.ContactDetailFragment;
+import com.android.contacts.views.editor.ContactEditorFragment;
import com.android.contacts.widget.SearchEditText;
import com.android.contacts.widget.SearchEditText.OnFilterTextListener;
@@ -28,14 +29,21 @@
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
+import android.widget.LinearLayout;
import android.widget.Toast;
+import android.widget.LinearLayout.LayoutParams;
public class TwoPaneActivity extends Activity {
private final static String TAG = "TwoPaneActivity";
+
private DefaultContactBrowseListFragment mListFragment;
+ private ListFragmentListener mListFragmentListener = new ListFragmentListener();
+
private ContactDetailFragment mDetailFragment;
private DetailFragmentListener mDetailFragmentListener = new DetailFragmentListener();
- private ListFragmentListener mListFragmentListener = new ListFragmentListener();
+
+ private ContactEditorFragment mEditorFragment;
+ private EditorFragmentListener mEditorFragmentListener = new EditorFragmentListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -46,12 +54,51 @@
mListFragment = (DefaultContactBrowseListFragment) findFragmentById(R.id.two_pane_list);
mListFragment.setOnContactListActionListener(mListFragmentListener);
- mDetailFragment = (ContactDetailFragment) findFragmentById(R.id.two_pane_detail);
- mDetailFragment.setListener(mDetailFragmentListener);
+ setupContactDetailFragment();
setupSearchUI();
}
+ private void setupContactDetailFragment() {
+ // No editor here
+ if (mEditorFragment != null) {
+ mEditorFragment.setListener(null);
+ mEditorFragment = null;
+ }
+
+ // Already showing? Nothing to do
+ if (mDetailFragment != null) return;
+
+ mDetailFragment = new ContactDetailFragment();
+ mDetailFragment.setListener(mDetailFragmentListener);
+
+ // Nothing showing yet? Create (this happens during Activity-Startup)
+ openFragmentTransaction()
+ .replace(R.id.two_pane_right_view, mDetailFragment)
+ .commit();
+
+ }
+
+ private void setupContactEditorFragment() {
+ // No detail view here
+ if (mDetailFragment != null) {
+ mDetailFragment.setListener(null);
+ mDetailFragment = null;
+ }
+
+ // Already showing? Nothing to do
+ if (mEditorFragment != null) return;
+
+ mEditorFragment = new ContactEditorFragment();
+ mEditorFragment.setListener(mEditorFragmentListener);
+
+ // Nothing showing yet? Create (this happens during Activity-Startup)
+ openFragmentTransaction()
+ .replace(R.id.two_pane_right_view, mEditorFragment)
+ .commit();
+
+ }
+
private void setupSearchUI() {
SearchEditText searchEditText = (SearchEditText)findViewById(R.id.search_src_text);
searchEditText.setOnFilterTextListener(new OnFilterTextListener() {
@@ -112,6 +159,7 @@
}
public void onViewContactAction(Uri contactLookupUri) {
+ setupContactDetailFragment();
mDetailFragment.loadUri(contactLookupUri);
}
}
@@ -122,10 +170,8 @@
}
public void onEditRequested(Uri contactLookupUri) {
-// final ContactEditFragment fragment = new ContactEditFragment();
-// openFragmentTransaction().replace(mDetailFragment.getView().getId(), fragment).commit();
-// fragment.loadUri(contactLookupUri);
- Toast.makeText(TwoPaneActivity.this, "editContact", Toast.LENGTH_LONG).show();
+ setupContactEditorFragment();
+ mEditorFragment.loadUri(contactLookupUri);
}
public void onItemClicked(Intent intent) {
@@ -136,4 +182,22 @@
showDialog(id, bundle);
}
}
+
+ private class EditorFragmentListener implements ContactEditorFragment.Listener {
+ public void onContactNotFound() {
+ Toast.makeText(TwoPaneActivity.this, "onContactNotFound", Toast.LENGTH_LONG).show();
+ }
+
+ public void onDialogRequested(int id, Bundle bundle) {
+ Toast.makeText(TwoPaneActivity.this, "onDialogRequested", Toast.LENGTH_LONG).show();
+ }
+
+ public void onEditorRequested(Intent intent) {
+ Toast.makeText(TwoPaneActivity.this, "onEditorRequested", Toast.LENGTH_LONG).show();
+ }
+
+ public void onError() {
+ Toast.makeText(TwoPaneActivity.this, "onError", Toast.LENGTH_LONG).show();
+ }
+ }
}
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index f7f4fdb..ff402a2 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -23,17 +23,20 @@
import android.content.Context;
import android.content.CursorLoader;
+import android.content.pm.PackageManager;
import android.database.Cursor;
+import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract.ContactCounts;
+import android.provider.ContactsContract.Directory;
import android.text.TextUtils;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-import java.util.HashMap;
-import java.util.Iterator;
+import java.util.HashSet;
/**
* Common base class for various contact-related lists, e.g. contact list, phone number list
@@ -43,6 +46,23 @@
private static final String TAG = "ContactEntryListAdapter";
+ private static final class DirectoryQuery {
+ public static final Uri URI = Directory.CONTENT_URI;
+ public static final String ORDER_BY = Directory._ID;
+
+ public static final String[] PROJECTION = {
+ Directory._ID,
+ Directory.PACKAGE_NAME,
+ Directory.TYPE_RESOURCE_ID,
+ Directory.DISPLAY_NAME,
+ };
+
+ public static final int ID = 0;
+ public static final int PACKAGE_NAME = 1;
+ public static final int TYPE_RESOURCE_ID = 2;
+ public static final int DISPLAY_NAME = 3;
+ }
+
/**
* The animation is used here to allocate animated name text views.
*/
@@ -61,28 +81,34 @@
private boolean mLoading = true;
private boolean mEmptyListEnabled = true;
- private HashMap<Integer, DirectoryPartition> mPartitions;
-
public ContactEntryListAdapter(Context context) {
super(context, R.layout.list_section, R.id.header_text);
addPartitions();
}
- /**
- * Adds all partitions this adapter will handle. The default implementation
- * creates one partition with no header.
- */
protected void addPartitions() {
- addPartition(false, false);
+ addPartition(createDefaultDirectoryPartition());
}
- public void addDirectoryPartition(DirectoryPartition partition) {
- if (mPartitions == null) {
- mPartitions = new HashMap<Integer, DirectoryPartition>();
+ protected DirectoryPartition createDefaultDirectoryPartition() {
+ DirectoryPartition partition = new DirectoryPartition(true, true);
+ partition.setDirectoryId(Directory.DEFAULT);
+ partition.setDirectoryType(getContext().getString(R.string.contactsList));
+ partition.setPriorityDirectory(true);
+ return partition;
+ }
+
+ private int getPartitionByDirectoryId(long id) {
+ int count = getPartitionCount();
+ for (int i = 0; i < count; i++) {
+ Partition partition = getPartition(i);
+ if (partition instanceof DirectoryPartition) {
+ if (((DirectoryPartition)partition).getDirectoryId() == id) {
+ return i;
+ }
+ }
}
- int partitionIndex = getPartitionCount();
- mPartitions.put(partitionIndex, partition);
- addPartition(partition.getShowIfEmpty(), partition.getDirectoryType() != null);
+ return -1;
}
public abstract String getContactDisplayName(int position);
@@ -93,10 +119,13 @@
*/
public void onDataReload() {
boolean notify = false;
- if (mPartitions != null) {
- for (DirectoryPartition partition : mPartitions.values()) {
- if (!partition.isLoading()) {
- partition.setLoading(true);
+ int count = getPartitionCount();
+ for (int i = 0; i < count; i++) {
+ Partition partition = getPartition(i);
+ if (partition instanceof DirectoryPartition) {
+ DirectoryPartition directoryPartition = (DirectoryPartition)partition;
+ if (!directoryPartition.isLoading()) {
+ directoryPartition.setLoading(true);
notify = true;
}
}
@@ -111,12 +140,21 @@
}
public void setSearchMode(boolean flag) {
- if (mSearchMode != flag) {
- mSearchMode = flag;
+ mSearchMode = flag;
- // Search mode change may mean a new data type in the list.
- // Let's drop current data to avoid confusion
- resetPartitions();
+ int defaultPartitionIndex = -1;
+ int count = getPartitionCount();
+ for (int i = 0; i < count; i++) {
+ Partition partition = getPartition(i);
+ if (partition instanceof DirectoryPartition &&
+ ((DirectoryPartition)partition).getDirectoryId() == Directory.DEFAULT) {
+ defaultPartitionIndex = i;
+ break;
+ }
+ }
+ if (defaultPartitionIndex != -1) {
+ setShowIfEmpty(defaultPartitionIndex, flag);
+ setHasHeader(defaultPartitionIndex, flag);
}
}
@@ -192,11 +230,74 @@
mEmptyListEnabled = flag;
}
+ public void configureDirectoryLoader(CursorLoader loader) {
+ loader.setUri(DirectoryQuery.URI);
+ loader.setProjection(DirectoryQuery.PROJECTION);
+ loader.setSortOrder(DirectoryQuery.ORDER_BY);
+ }
+
+ /**
+ * Updates partitions according to the directory meta-data contained in the supplied
+ * cursor. Takes ownership of the cursor and will close it.
+ */
+ public void changeDirectories(Cursor cursor) {
+ HashSet<Long> directoryIds = new HashSet<Long>();
+
+ // TODO preserve the order of partition to match those of the cursor
+ // Phase I: add new directories
+ try {
+ while (cursor.moveToNext()) {
+ long id = cursor.getLong(DirectoryQuery.ID);
+ directoryIds.add(id);
+ if (getPartitionByDirectoryId(id) == -1) {
+ DirectoryPartition partition = createDirectoryPartition(cursor);
+ addPartition(partition);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+
+ // Phase II: remove deleted directories
+ int count = getPartitionCount();
+ for (int i = count; --i >= 0; ) {
+ Partition partition = getPartition(i);
+ if (partition instanceof DirectoryPartition) {
+ long id = ((DirectoryPartition)partition).getDirectoryId();
+ if (!directoryIds.contains(id)) {
+ removePartition(i);
+ }
+ }
+ }
+
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ private DirectoryPartition createDirectoryPartition(Cursor cursor) {
+ PackageManager pm = getContext().getPackageManager();
+ DirectoryPartition partition = new DirectoryPartition(false, true);
+ partition.setDirectoryId(cursor.getLong(DirectoryQuery.ID));
+ String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
+ int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
+ if (!TextUtils.isEmpty(packageName) && typeResourceId != 0) {
+ // TODO: should this be done on a background thread?
+ try {
+ partition.setDirectoryType(pm.getResourcesForApplication(packageName)
+ .getString(typeResourceId));
+ } catch (Exception e) {
+ Log.e(TAG, "Cannot obtain directory type from package: " + packageName);
+ }
+ }
+ partition.setDisplayName(cursor.getString(DirectoryQuery.DISPLAY_NAME));
+ return partition;
+ }
+
@Override
public void changeCursor(int partitionIndex, Cursor cursor) {
- if (mPartitions != null) {
- DirectoryPartition partition = mPartitions.get(partitionIndex);
- partition.setLoading(false);
+ Partition partition = getPartition(partitionIndex);
+ if (partition instanceof DirectoryPartition) {
+ ((DirectoryPartition)partition).setLoading(false);
}
super.changeCursor(partitionIndex, cursor);
@@ -258,20 +359,24 @@
@Override
protected void bindHeaderView(View view, int partitionIndex, Cursor cursor) {
- DirectoryPartition partition = mPartitions.get(partitionIndex);
+ Partition partition = getPartition(partitionIndex);
+ if (!(partition instanceof DirectoryPartition)) {
+ return;
+ }
+ DirectoryPartition directoryPartition = (DirectoryPartition)partition;
TextView directoryTypeTextView = (TextView)view.findViewById(R.id.directory_type);
- directoryTypeTextView.setText(partition.getDirectoryType());
+ directoryTypeTextView.setText(directoryPartition.getDirectoryType());
TextView displayNameTextView = (TextView)view.findViewById(R.id.display_name);
- if (!TextUtils.isEmpty(partition.getDisplayName())) {
- displayNameTextView.setText(partition.getDisplayName());
+ if (!TextUtils.isEmpty(directoryPartition.getDisplayName())) {
+ displayNameTextView.setText(directoryPartition.getDisplayName());
displayNameTextView.setVisibility(View.VISIBLE);
} else {
displayNameTextView.setVisibility(View.GONE);
}
TextView countText = (TextView)view.findViewById(R.id.count);
- if (partition.isLoading()) {
+ if (directoryPartition.isLoading()) {
countText.setText(R.string.search_results_searching);
} else {
int count = cursor == null ? 0 : cursor.getCount();
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index 8c2abb3..9bfdbb3 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -23,7 +23,7 @@
import com.android.contacts.R;
import com.android.contacts.ui.ContactsPreferences;
import com.android.contacts.widget.ContextMenuAdapter;
-import com.google.android.collect.Lists;
+import com.android.contacts.widget.CompositeCursorAdapter.Partition;
import android.accounts.Account;
import android.accounts.AccountManager;
@@ -36,10 +36,8 @@
import android.content.IContentService;
import android.content.Intent;
import android.content.Loader;
-import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.database.Cursor;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
@@ -68,8 +66,6 @@
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView.OnItemClickListener;
-import java.util.ArrayList;
-
/**
* Common base class for various contact-related list fragments.
*/
@@ -87,8 +83,6 @@
private static final int DIRECTORY_LOADER_ID = -1;
- private ArrayList<DirectoryPartition> mDirectoryPartitions = Lists.newArrayList();
-
private boolean mSectionHeaderDisplayEnabled;
private boolean mPhotoLoaderEnabled;
private boolean mSearchMode;
@@ -118,31 +112,17 @@
private int mProviderStatus = ProviderStatus.STATUS_NORMAL;
+ private boolean mForceLoad;
+ private boolean mLoadDirectoryList;
+
/**
* Indicates whether we are doing the initial complete load of data or
* a refresh caused by a change notification.
*/
- private boolean mInitialLoadComplete;
+ private boolean mLoadPriorityDirectoriesOnly;
private ContactsRequest mRequest;
- private static final class DirectoryQuery {
- public static final Uri URI = Directory.CONTENT_URI;
- public static final String ORDER_BY = Directory._ID;
-
- public static final String[] PROJECTION = {
- Directory._ID,
- Directory.PACKAGE_NAME,
- Directory.TYPE_RESOURCE_ID,
- Directory.DISPLAY_NAME,
- };
-
- public static final int ID = 0;
- public static final int PACKAGE_NAME = 1;
- public static final int TYPE_RESOURCE_ID = 2;
- public static final int DISPLAY_NAME = 3;
- }
-
protected abstract View inflateView(LayoutInflater inflater, ViewGroup container);
protected abstract T createListAdapter();
@@ -200,42 +180,58 @@
loadPreferences(mContactsPrefs);
- configureAdapter();
-
- if (isSearchMode()) {
- if (mInitialLoadComplete) {
- for (DirectoryPartition partition : mDirectoryPartitions) {
- if (partition.getPartitionIndex() != 0) {
- startLoading(partition, true);
- }
- }
- } else {
- startLoading(0, null);
- }
- } else {
- startLoading(0, null);
+ if (mListView instanceof ContactEntryListView) {
+ ContactEntryListView listView = (ContactEntryListView)mListView;
+ listView.setHighlightNamesWhenScrolling(isNameHighlighingEnabled());
}
- ContactEntryListView listView = (ContactEntryListView)mListView;
- listView.setHighlightNamesWhenScrolling(isNameHighlighingEnabled());
-
+ mForceLoad = false;
+ mLoadDirectoryList = true;
+ mLoadPriorityDirectoriesOnly = true;
+ startLoading();
super.onStart();
}
+ private void startLoading() {
+ configureAdapter();
+ int partitionCount = mAdapter.getPartitionCount();
+ for (int i = 0; i < partitionCount; i++) {
+ Partition partition = mAdapter.getPartition(i);
+ if (partition instanceof DirectoryPartition) {
+ DirectoryPartition directoryPartition = (DirectoryPartition)partition;
+ if (mLoadPriorityDirectoriesOnly == directoryPartition.isPriorityDirectory()) {
+ startLoadingDirectoryPartition(i);
+ }
+ } else {
+ startLoading(i, null);
+ }
+ }
+ }
+
@Override
protected Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ CursorLoader loader = new CursorLoader(getActivity(), null, null, null, null, null);
if (id == DIRECTORY_LOADER_ID) {
- return new CursorLoader(getActivity(), DirectoryQuery.URI, DirectoryQuery.PROJECTION,
- null, null, DirectoryQuery.ORDER_BY);
+ mAdapter.configureDirectoryLoader(loader);
} else {
- CursorLoader loader = new CursorLoader(getActivity(), null, null, null, null, null);
- if (mAdapter != null) {
- long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY)
- ? args.getLong(DIRECTORY_ID_ARG_KEY)
- : Directory.DEFAULT;
- mAdapter.configureLoader(loader, directoryId);
- }
- return loader;
+ long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY)
+ ? args.getLong(DIRECTORY_ID_ARG_KEY)
+ : Directory.DEFAULT;
+ mAdapter.configureLoader(loader, directoryId);
+ }
+ return loader;
+ }
+
+ private void startLoadingDirectoryPartition(int partitionIndex) {
+ DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);
+ CursorLoader loader = (CursorLoader)getLoader(partitionIndex);
+ if (loader == null) {
+ Bundle args = new Bundle();
+ args.putLong(DIRECTORY_ID_ARG_KEY, partition.getDirectoryId());
+ startLoading(partitionIndex, args);
+ } else if (mForceLoad) {
+ mAdapter.configureLoader(loader, partition.getDirectoryId());
+ loader.forceLoad();
}
}
@@ -248,15 +244,27 @@
return;
}
- int partitionIndex = loader.getId();
- if (!mInitialLoadComplete) {
- onInitialLoadFinished(partitionIndex, data);
+ int loaderId = loader.getId();
+ if (loaderId == DIRECTORY_LOADER_ID) {
+ mAdapter.changeDirectories(data);
} else {
- onRequeryFinished(partitionIndex, data);
+ final int partitionIndex = loaderId; // by convention
+ mAdapter.changeCursor(partitionIndex, data);
+ showCount(partitionIndex, data);
+ if (partitionIndex == mAdapter.getIndexedPartition()) {
+ mAizy.setIndexer(mAdapter.getIndexer());
+ }
+ completeRestoreInstanceState();
}
- if (partitionIndex == mAdapter.getIndexedPartition()) {
- mAizy.setIndexer(mAdapter.getIndexer());
+ if (isSearchMode()) {
+ if (mLoadDirectoryList) {
+ mLoadDirectoryList = false;
+ startLoading(DIRECTORY_LOADER_ID, null);
+ } else if (mLoadPriorityDirectoriesOnly) {
+ mLoadPriorityDirectoriesOnly = false;
+ startLoading();
+ }
}
// TODO fix the empty view
@@ -265,149 +273,28 @@
// }
}
- private void onInitialLoadFinished(int partitionIndex, Cursor data) {
- if (partitionIndex == 0) {
- mDirectoryPartitions.clear();
- mAdapter.resetPartitions();
- DirectoryPartition partition = new DirectoryPartition();
- partition.setDirectoryId(Directory.DEFAULT);
- partition.setShowIfEmpty(isSearchMode());
- partition.setDirectoryType(getActivity().getString(R.string.contactsList));
- mDirectoryPartitions.add(partition);
- if (isSearchMode()) {
- mAdapter.addDirectoryPartition(partition);
- } else {
- mAdapter.addPartition(false, false);
- }
- mAdapter.changeCursor(partitionIndex, data);
- showCount(partitionIndex, data);
- if (data != null) {
- completeRestoreInstanceState();
- }
- if (isSearchMode()) {
- startLoading(DIRECTORY_LOADER_ID, null);
- } else {
- mInitialLoadComplete = true;
- }
- } else if (partitionIndex == DIRECTORY_LOADER_ID) {
- try {
- for (int index = 0; data.moveToNext(); index++) {
- DirectoryPartition partition = createDirectoryPartition(index, data);
- if (index != 0) {
- mDirectoryPartitions.add(partition);
- mAdapter.addDirectoryPartition(partition);
- startLoading(partition, false);
- }
- }
- } finally {
- data.close();
- }
-
- mInitialLoadComplete = true;
- }
- }
-
- private void onRequeryFinished(int partitionIndex, Cursor data) {
- if (partitionIndex == 0) {
- mAdapter.changeCursor(partitionIndex, data);
- showCount(partitionIndex, data);
- int size = mDirectoryPartitions.size();
- for (int i = 1; i < size; i++) {
- startLoading(mDirectoryPartitions.get(i), true);
- }
- } else if (partitionIndex == DIRECTORY_LOADER_ID) {
- // The list of available directories has changed: reload everything
- try {
- mDirectoryPartitions.clear();
- mAdapter.resetPartitions();
- for (int index = 0; data.moveToNext(); index++) {
- DirectoryPartition partition = createDirectoryPartition(index, data);
- mDirectoryPartitions.add(partition);
- mAdapter.addDirectoryPartition(partition);
- }
- } finally {
- data.close();
- }
- reloadData();
- } else {
- mAdapter.changeCursor(partitionIndex, data);
- showCount(partitionIndex, data);
- }
- }
-
- private DirectoryPartition createDirectoryPartition(int partitionIndex, Cursor cursor) {
- PackageManager pm = getActivity().getPackageManager();
- DirectoryPartition partition = new DirectoryPartition();
- partition.setPartitionIndex(partitionIndex);
- partition.setDirectoryId(cursor.getLong(DirectoryQuery.ID));
- String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
- int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
- if (!TextUtils.isEmpty(packageName) && typeResourceId != 0) {
- // TODO: should this be done on a background thread?
- try {
- partition.setDirectoryType(pm.getResourcesForApplication(packageName)
- .getString(typeResourceId));
- } catch (Exception e) {
- Log.e(TAG, "Cannot obtain directory type from package: " + packageName);
- }
- }
- partition.setDisplayName(cursor.getString(DirectoryQuery.DISPLAY_NAME));
- partition.setShowIfEmpty(partition.getDirectoryId() == Directory.DEFAULT);
- return partition;
- }
-
- private void startLoading(DirectoryPartition partition, boolean forceLoad) {
- CursorLoader loader = (CursorLoader)getLoader(partition.getPartitionIndex());
- if (loader == null) {
- Bundle args = new Bundle();
- args.putLong(DIRECTORY_ID_ARG_KEY, partition.getDirectoryId());
- startLoading(partition.getPartitionIndex(), args);
- } else {
- mAdapter.configureLoader(loader, partition.getDirectoryId());
- if (forceLoad) {
- loader.forceLoad();
- }
- }
- }
-
protected void reloadData() {
- if (mInitialLoadComplete) {
- mAdapter.onDataReload();
- if (mDirectoryPartitions.size() > 0) {
- // We need to cancel _all_ current queries and then launch
- // a new query for the 0th partition.
-
- CursorLoader directoryLoader = (CursorLoader)getLoader(DIRECTORY_LOADER_ID);
- if (directoryLoader != null) {
- directoryLoader.cancelLoad();
- }
- int size = mDirectoryPartitions.size();
- for (int i = 0; i < size; i++) {
- CursorLoader loader = (CursorLoader)getLoader(i);
- if (loader != null) {
- loader.cancelLoad();
- }
- }
-
- startLoading(mDirectoryPartitions.get(0), true);
- }
- } else {
- startLoading(0, null);
- }
+ cancelLoading();
+ mAdapter.onDataReload();
+ mLoadPriorityDirectoriesOnly = true;
+ mForceLoad = true;
+ startLoading();
}
- private void stopLoading() {
- stopLoading(DIRECTORY_LOADER_ID);
- for (DirectoryPartition partition : mDirectoryPartitions) {
- stopLoading(partition.getPartitionIndex());
+ private void cancelLoading() {
+ int size = mAdapter.getPartitionCount();
+ for (int i = 0; i < size; i++) {
+ CursorLoader loader = (CursorLoader)getLoader(i);
+ if (loader != null) {
+ loader.cancelLoad();
+ }
}
}
@Override
public void onStop() {
super.onStop();
- mAdapter.resetPartitions();
- mInitialLoadComplete = false;
+ mAdapter.clearPartitions();
}
/**
@@ -476,14 +363,15 @@
public void setSearchMode(boolean flag) {
if (mSearchMode != flag) {
mSearchMode = flag;
- // TODO not always
setSectionHeaderDisplayEnabled(!mSearchMode);
+
if (mAdapter != null) {
- stopLoading();
+ mAdapter.clearPartitions();
mAdapter.setSearchMode(flag);
mAdapter.setPinnedPartitionHeadersEnabled(flag);
- mInitialLoadComplete = false;
+ reloadData();
}
+
if (mListView != null) {
mListView.setFastScrollEnabled(!flag);
}
@@ -665,6 +553,7 @@
if (mAdapter == null) {
return;
}
+
mAdapter.setQueryString(mQueryString);
mAdapter.setPinnedPartitionHeadersEnabled(mSearchMode);
mAdapter.setContactNameDisplayOrder(mDisplayOrder);
diff --git a/src/com/android/contacts/list/DirectoryPartition.java b/src/com/android/contacts/list/DirectoryPartition.java
index b76b97a..d7cb9bc 100644
--- a/src/com/android/contacts/list/DirectoryPartition.java
+++ b/src/com/android/contacts/list/DirectoryPartition.java
@@ -15,19 +15,23 @@
*/
package com.android.contacts.list;
-import android.database.Cursor;
+import com.android.contacts.widget.CompositeCursorAdapter;
+
import android.provider.ContactsContract.Directory;
/**
* Model object for a {@link Directory} row.
*/
-public final class DirectoryPartition {
+public final class DirectoryPartition extends CompositeCursorAdapter.Partition {
private long mDirectoryId;
- private int mPartitionIndex;
private String mDirectoryType;
private String mDisplayName;
- private boolean mShowIfEmpty;
private boolean mLoading;
+ private boolean mPriorityDirectory;
+
+ public DirectoryPartition(boolean showIfEmpty, boolean hasHeader) {
+ super(showIfEmpty, hasHeader);
+ }
/**
* Directory ID, see {@link Directory}.
@@ -41,17 +45,6 @@
}
/**
- * Corresponding loader ID.
- */
- public int getPartitionIndex() {
- return mPartitionIndex;
- }
-
- public void setPartitionIndex(int partitionIndex) {
- this.mPartitionIndex = partitionIndex;
- }
-
- /**
* Directory type resolved from {@link Directory#PACKAGE_NAME} and
* {@link Directory#TYPE_RESOURCE_ID};
*/
@@ -74,17 +67,6 @@
this.mDisplayName = displayName;
}
- /**
- * True if the directory should be shown even if no contacts are found.
- */
- public boolean getShowIfEmpty() {
- return mShowIfEmpty;
- }
-
- public void setShowIfEmpty(boolean showIfEmpty) {
- this.mShowIfEmpty = showIfEmpty;
- }
-
public boolean isLoading() {
return mLoading;
}
@@ -92,4 +74,15 @@
public void setLoading(boolean loading) {
mLoading = loading;
}
+
+ /**
+ * Returns true if this directory should be loaded before non-priority directories.
+ */
+ public boolean isPriorityDirectory() {
+ return mPriorityDirectory;
+ }
+
+ public void setPriorityDirectory(boolean priorityDirectory) {
+ mPriorityDirectory = priorityDirectory;
+ }
}
diff --git a/src/com/android/contacts/model/EntityDelta.java b/src/com/android/contacts/model/EntityDelta.java
index 9eb7779..cdf2e41 100644
--- a/src/com/android/contacts/model/EntityDelta.java
+++ b/src/com/android/contacts/model/EntityDelta.java
@@ -59,7 +59,7 @@
// TODO: optimize by using contentvalues pool, since we allocate so many of them
private static final String TAG = "EntityDelta";
- private static final boolean LOGV = true;
+ private static final boolean LOGV = false;
/**
* Direct values from {@link Entity#getEntityValues()}.
diff --git a/src/com/android/contacts/vcard/ExportProcessor.java b/src/com/android/contacts/vcard/ExportProcessor.java
new file mode 100644
index 0000000..7e7c7c8
--- /dev/null
+++ b/src/com/android/contacts/vcard/ExportProcessor.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.vcard;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import com.android.contacts.ContactsListActivity;
+import com.android.contacts.R;
+import com.android.vcard.VCardComposer;
+import com.android.vcard.VCardConfig;
+
+import java.io.FileNotFoundException;
+import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class ExportProcessor {
+ private static final String LOG_TAG = "ExportProcessor";
+
+ private final Service mService;
+
+ private ContentResolver mResolver;
+ private NotificationManager mNotificationManager;
+
+ boolean mCanceled;
+
+ boolean mReadyForRequest;
+ private final Queue<ExportRequest> mPendingRequests =
+ new LinkedList<ExportRequest>();
+
+ private RemoteViews mProgressViews;
+
+ public ExportProcessor(Service service) {
+ mService = service;
+ }
+
+ /* package */ ThreadStarter mThreadStarter = new ThreadStarter() {
+ public void start() {
+ final Thread thread = new Thread(new Runnable() {
+ public void run() {
+ process();
+ }
+ });
+ thread.start();
+ }
+ };
+
+ public synchronized void pushRequest(ExportRequest parameter) {
+ if (mResolver == null) {
+ // Service object may not ready at the construction time
+ // (e.g. ContentResolver may be null).
+ mResolver = mService.getContentResolver();
+ mNotificationManager =
+ (NotificationManager)mService.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ final boolean needThreadStart;
+ if (!mReadyForRequest) {
+ needThreadStart = true;
+ } else {
+ needThreadStart = false;
+ }
+ mPendingRequests.add(parameter);
+ if (needThreadStart) {
+ mThreadStarter.start();
+ }
+
+ mReadyForRequest = true;
+ }
+
+ /* package */ void process() {
+ if (!mReadyForRequest) {
+ throw new RuntimeException(
+ "process() is called before request being pushed "
+ + "or after this object's finishing its processing.");
+ }
+
+ try {
+ while (!mCanceled) {
+ final ExportRequest parameter;
+ synchronized (this) {
+ if (mPendingRequests.size() == 0) {
+ mReadyForRequest = false;
+ break;
+ } else {
+ parameter = mPendingRequests.poll();
+ }
+ } // synchronized (this)
+ handleOneRequest(parameter);
+ }
+
+ doFinishNotification(mService.getString(R.string.exporting_vcard_finished_title),
+ "");
+ } finally {
+ // Not thread safe. Just in case.
+ // TODO: verify this works fine.
+ mReadyForRequest = false;
+ }
+ }
+
+ /* package */ void handleOneRequest(ExportRequest request) {
+ boolean shouldCallFinish = true;
+ VCardComposer composer = null;
+ try {
+ final Uri uri = request.destUri;
+ final OutputStream outputStream;
+ try {
+ outputStream = mResolver.openOutputStream(uri);
+ } catch (FileNotFoundException e) {
+ // Need concise title.
+
+ final String errorReason =
+ mService.getString(R.string.fail_reason_could_not_open_file,
+ uri, e.getMessage());
+ shouldCallFinish = false;
+ doFinishNotification(errorReason, "");
+ return;
+ }
+ final String exportType = request.exportType;
+ final int vcardType;
+ if (TextUtils.isEmpty(exportType)) {
+ vcardType = VCardConfig.getVCardTypeFromString(
+ mService.getString(R.string.config_export_vcard_type));
+ } else {
+ vcardType = VCardConfig.getVCardTypeFromString(exportType);
+ }
+
+ composer = new VCardComposer(mService, vcardType, true);
+
+ // for test
+ // int vcardType = (VCardConfig.VCARD_TYPE_V21_GENERIC |
+ // VCardConfig.FLAG_USE_QP_TO_PRIMARY_PROPERTIES);
+ // composer = new VCardComposer(ExportVCardActivity.this, vcardType, 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);
+ final String title =
+ mService.getString(R.string.fail_reason_could_not_initialize_exporter,
+ translatedErrorReason);
+ doFinishNotification(title, "");
+ return;
+ }
+
+ final int total = composer.getCount();
+ if (total == 0) {
+ final String title =
+ mService.getString(R.string.fail_reason_no_exportable_contact);
+ doFinishNotification(title, "");
+ return;
+ }
+
+ int current = 1; // 1-origin
+ 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);
+ final String title =
+ mService.getString(R.string.fail_reason_error_occurred_during_export,
+ translatedErrorReason);
+ doFinishNotification(title, "");
+ return;
+ }
+ doProgressNotification(uri, total, current);
+ current++;
+ }
+ } finally {
+ if (composer != null) {
+ composer.terminate();
+ }
+ }
+ }
+
+ private String translateComposerError(String errorMessage) {
+ final Resources resources = mService.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;
+ }
+ }
+
+ private void doProgressNotification(Uri uri, int total, int current) {
+ final String title = mService.getString(R.string.exporting_contact_list_title);
+ final String message =
+ mService.getString(R.string.exporting_contact_list_message, uri);
+
+ final RemoteViews remoteViews = new RemoteViews(mService.getPackageName(),
+ R.layout.status_bar_ongoing_event_progress_bar);
+ remoteViews.setTextViewText(R.id.description, message);
+ remoteViews.setProgressBar(R.id.progress_bar, total, current, (total == -1));
+
+ final String percentage = mService.getString(R.string.percentage,
+ String.valueOf((current * 100)/total));
+ remoteViews.setTextViewText(R.id.progress_text, percentage);
+ remoteViews.setImageViewResource(R.id.appIcon, android.R.drawable.stat_sys_upload);
+
+ final Notification notification = new Notification();
+ notification.icon = android.R.drawable.stat_sys_upload;
+ notification.flags |= Notification.FLAG_ONGOING_EVENT;
+ notification.tickerText = title;
+ notification.contentView = remoteViews;
+ notification.contentIntent =
+ PendingIntent.getActivity(mService, 0,
+ new Intent(mService, ContactsListActivity.class), 0);
+ mNotificationManager.notify(VCardService.EXPORT_NOTIFICATION_ID, notification);
+ }
+
+ private void doFinishNotification(final String title, final String message) {
+ final Notification notification = new Notification();
+ notification.icon = android.R.drawable.stat_sys_upload_done;
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+ notification.setLatestEventInfo(mService, title, message, null);
+ final Intent intent = new Intent(mService, ContactsListActivity.class);
+ notification.contentIntent =
+ PendingIntent.getActivity(mService, 0, intent, 0);
+ mNotificationManager.notify(VCardService.EXPORT_NOTIFICATION_ID, notification);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/vcard/ExportRequest.java b/src/com/android/contacts/vcard/ExportRequest.java
new file mode 100644
index 0000000..fae2d07
--- /dev/null
+++ b/src/com/android/contacts/vcard/ExportRequest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.vcard;
+
+import android.net.Uri;
+
+public class ExportRequest {
+ public final Uri destUri;
+ /**
+ * Can be null.
+ */
+ public final String exportType;
+
+ public ExportRequest(Uri destUri) {
+ this(destUri, null);
+ }
+
+ public ExportRequest(Uri destUri, String exportType) {
+ this.destUri = destUri;
+ this.exportType = exportType;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/vcard/ExportVCardActivity.java b/src/com/android/contacts/vcard/ExportVCardActivity.java
index 509702b..43ae858 100644
--- a/src/com/android/contacts/vcard/ExportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ExportVCardActivity.java
@@ -19,12 +19,21 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
+import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.res.Resources;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
@@ -37,6 +46,8 @@
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Queue;
import java.util.Set;
/**
@@ -58,21 +69,76 @@
private int mFileIndexMinimum;
private int mFileIndexMaximum;
private String mFileNameExtension;
- private String mVCardTypeStr;
private Set<String> mExtensionsToConsider;
- private ProgressDialog mProgressDialog;
- private String mExportingFileName;
-
- private Handler mHandler = new Handler();
-
- // Used temporaly when asking users to confirm the file name
+ // Used temporarily when asking users to confirm the file name
private String mTargetFileName;
- // String for storing error reason temporaly.
+ // String for storing error reason temporarily.
private String mErrorReason;
- private ActualExportThread mActualExportThread;
+
+ private class CustomConnection implements ServiceConnection {
+ private Messenger mMessenger;
+ private Queue<ExportRequest> mPendingRequests = new LinkedList<ExportRequest>();
+
+ public void doBindService() {
+ bindService(new Intent(ExportVCardActivity.this,
+ VCardService.class), this, Context.BIND_AUTO_CREATE);
+ }
+
+ public synchronized void requestSend(final ExportRequest parameter) {
+ if (mMessenger != null) {
+ sendMessage(parameter);
+ } else {
+ mPendingRequests.add(parameter);
+ }
+ }
+
+ private void sendMessage(final ExportRequest request) {
+ try {
+ mMessenger.send(Message.obtain(null,
+ VCardService.MSG_EXPORT_REQUEST,
+ request));
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
+ runOnUiThread(new ErrorReasonDisplayer(
+ getString(R.string.fail_reason_unknown)));
+ }
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (this) {
+ mMessenger = new Messenger(service);
+ // Send pending requests thrown from this Activity before an actual connection
+ // is established.
+ while (!mPendingRequests.isEmpty()) {
+ final ExportRequest parameter = mPendingRequests.poll();
+ if (parameter == null) {
+ throw new NullPointerException();
+ }
+ sendMessage(parameter);
+ }
+
+ unbindService(this);
+ finish();
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (this) {
+ if (!mPendingRequests.isEmpty()) {
+ Log.w(LOG_TAG, "Some request(s) are dropped.");
+ }
+ // Set to null so that we can detect inappropriate re-connection toward
+ // the Service via NullPointerException;
+ mPendingRequests = null;
+ mMessenger = null;
+ }
+ }
+ }
+
+ private final CustomConnection mConnection = new CustomConnection();
private class CancelListener
implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
@@ -104,130 +170,28 @@
}
private class ExportConfirmationListener implements DialogInterface.OnClickListener {
- private final String mFileName;
+ private final Uri mDestUri;
public ExportConfirmationListener(String fileName) {
- mFileName = fileName;
+ this(Uri.parse("file://" + fileName));
+ }
+
+ public ExportConfirmationListener(Uri uri) {
+ mDestUri = uri;
}
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
- mActualExportThread = new ActualExportThread(mFileName);
- showDialog(R.id.dialog_exporting_vcard);
+ mConnection.doBindService();
+
+ final ExportRequest request = new ExportRequest(mDestUri);
+
+ // The connection object will call finish().
+ mConnection.requestSend(request);
}
}
}
- private class ActualExportThread extends Thread
- implements DialogInterface.OnCancelListener {
- private PowerManager.WakeLock mWakeLock;
- private boolean mCanceled = false;
-
- public ActualExportThread(String fileName) {
- mExportingFileName = 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(mExportingFileName);
- } catch (FileNotFoundException e) {
- final String errorReason =
- getString(R.string.fail_reason_could_not_open_file,
- mExportingFileName, e.getMessage());
- shouldCallFinish = false;
- mHandler.post(new ErrorReasonDisplayer(errorReason));
- return;
- }
-
- final int vcardType = VCardConfig.getVCardTypeFromString(mVCardTypeStr);
- composer = new VCardComposer(ExportVCardActivity.this, vcardType, true);
- /*int vcardType = (VCardConfig.VCARD_TYPE_V21_GENERIC |
- VCardConfig.FLAG_USE_QP_TO_PRIMARY_PROPERTIES);
- composer = new VCardComposer(ExportVCardActivity.this, vcardType, 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)) {
@@ -249,7 +213,6 @@
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);
@@ -273,7 +236,7 @@
}
@Override
- protected Dialog onCreateDialog(int id) {
+ protected Dialog onCreateDialog(int id, Bundle bundle) {
switch (id) {
case R.id.dialog_export_confirmation: {
return getExportConfirmationDialog();
@@ -297,44 +260,25 @@
.setPositiveButton(android.R.string.ok, mCancelListener);
return builder.create();
}
- case R.id.dialog_exporting_vcard: {
- if (mProgressDialog == null) {
- String title = getString(R.string.exporting_contact_list_title);
- String message = getString(R.string.exporting_contact_list_message,
- mExportingFileName);
- mProgressDialog = new ProgressDialog(ExportVCardActivity.this);
- mProgressDialog.setTitle(title);
- mProgressDialog.setMessage(message);
- mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- mProgressDialog.setOnCancelListener(mActualExportThread);
- mActualExportThread.start();
- }
- return mProgressDialog;
- }
}
- return super.onCreateDialog(id);
+ return super.onCreateDialog(id, bundle);
}
@Override
- protected void onPrepareDialog(int id, Dialog dialog) {
+ protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
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);
+ super.onPrepareDialog(id, dialog, args);
}
}
@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();
@@ -449,13 +393,6 @@
.create();
}
- public void cancelExport() {
- if (mActualExportThread != null) {
- mActualExportThread.cancel();
- mActualExportThread = null;
- }
- }
-
public String getErrorReason() {
return mErrorReason;
}
diff --git a/src/com/android/contacts/vcard/ImportRequestProcessor.java b/src/com/android/contacts/vcard/ImportProcessor.java
similarity index 92%
rename from src/com/android/contacts/vcard/ImportRequestProcessor.java
rename to src/com/android/contacts/vcard/ImportProcessor.java
index b4158b6..f4b5f5a 100644
--- a/src/com/android/contacts/vcard/ImportRequestProcessor.java
+++ b/src/com/android/contacts/vcard/ImportProcessor.java
@@ -53,7 +53,7 @@
* This class is designed so that a user ({@link Service}) does not need to (and should not)
* recreate multiple instances, as this holds total count of vCard entries to be imported.
*/
-public class ImportRequestProcessor {
+public class ImportProcessor {
private static final String LOG_TAG = "ImportRequestProcessor";
private final Service mService;
@@ -67,19 +67,6 @@
private VCardParser mVCardParser;
- // TODO(dmiyakawa): better design for testing?
- /* package */ interface CommitterGenerator {
- public VCardEntryCommitter generate(ContentResolver resolver);
- }
-
- private static class DefaultCommitterGenerator implements CommitterGenerator {
- public VCardEntryCommitter generate(ContentResolver resolver) {
- return new VCardEntryCommitter(resolver);
- }
- }
-
- /* package */ CommitterGenerator mCommitterGenerator = new DefaultCommitterGenerator();
-
/**
* Meaning a controller of this object requests the operation should be canceled
* or not, which implies {@link #mReadyForRequest} should be set to false soon, but
@@ -95,9 +82,7 @@
private final Queue<ImportRequest> mPendingRequests =
new LinkedList<ImportRequest>();
- /* package */ interface ThreadStarter {
- public void start();
- }
+ // For testability.
/* package */ ThreadStarter mThreadStarter = new ThreadStarter() {
public void start() {
final Thread thread = new Thread(new Runnable() {
@@ -108,9 +93,17 @@
thread.start();
}
};
+ /* package */ interface CommitterGenerator {
+ public VCardEntryCommitter generate(ContentResolver resolver);
+ }
+ /* package */ class DefaultCommitterGenerator implements CommitterGenerator {
+ public VCardEntryCommitter generate(ContentResolver resolver) {
+ return new VCardEntryCommitter(mResolver);
+ }
+ }
+ /* package */ CommitterGenerator mCommitterGenerator = new DefaultCommitterGenerator();
-
- public ImportRequestProcessor(final Service service) {
+ public ImportProcessor(final Service service) {
mService = service;
}
@@ -153,7 +146,8 @@
/* package */ void process() {
if (!mReadyForRequest) {
throw new RuntimeException(
- "process() is called after this object finishing its process.");
+ "process() is called before request being pushed "
+ + "or after this object's finishing its processing.");
}
try {
while (!mCanceled) {
@@ -172,7 +166,7 @@
// Currenty we don't have an appropriate way to let users see all entries
// imported in this procedure. Instead, we show them entries only when
// there's just one created uri.
- doFinishNotification(mCreatedUris.size() == 1 ? mCreatedUris.get(0) : null);
+ doFinishNotification(mCreatedUris.size() > 0 ? mCreatedUris.get(0) : null);
} finally {
// TODO: verify this works fine.
mReadyForRequest = false; // Just in case.
@@ -211,7 +205,7 @@
final String estimatedCharset = parameter.estimatedCharset;
final VCardEntryConstructor constructor =
- new VCardEntryConstructor(estimatedVCardType, account, estimatedCharset);
+ new VCardEntryConstructor(estimatedVCardType, account, estimatedCharset);
final VCardEntryCommitter committer = mCommitterGenerator.generate(mResolver);
constructor.addEntryHandler(committer);
constructor.addEntryHandler(mNotifier);
@@ -239,6 +233,7 @@
private void doErrorNotification(int id) {
final Notification notification = new Notification();
notification.icon = android.R.drawable.stat_sys_download_done;
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
final String title = mService.getString(R.string.reading_vcard_failed_title);
final PendingIntent intent =
PendingIntent.getActivity(mService, 0, new Intent(), 0);
@@ -250,7 +245,9 @@
private void doFinishNotification(Uri createdUri) {
final Notification notification = new Notification();
notification.icon = android.R.drawable.stat_sys_download_done;
- final String title = mService.getString(R.string.reading_vcard_finished_title);
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+
+ final String title = mService.getString(R.string.importing_vcard_finished_title);
final Intent intent;
if (createdUri != null) {
@@ -263,10 +260,9 @@
intent = null;
}
- final PendingIntent pendingIntent =
- PendingIntent.getActivity(mService, 0, intent, 0);
- notification.setLatestEventInfo(mService, title, "", pendingIntent);
- mNotificationManager.notify(ImportVCardService.NOTIFICATION_ID, notification);
+ notification.setLatestEventInfo(mService, title, "",
+ PendingIntent.getActivity(mService, 0, intent, 0));
+ mNotificationManager.notify(VCardService.IMPORT_NOTIFICATION_ID, notification);
}
private boolean readOneVCard(Uri uri, int vcardType, String charset,
diff --git a/src/com/android/contacts/vcard/ImportProgressNotifier.java b/src/com/android/contacts/vcard/ImportProgressNotifier.java
index 42d101b..e6f1037 100644
--- a/src/com/android/contacts/vcard/ImportProgressNotifier.java
+++ b/src/com/android/contacts/vcard/ImportProgressNotifier.java
@@ -56,38 +56,42 @@
// - We cannot know name there but here.
// - There's high probability where name comes soon after the beginning of entry, so
// we don't need to hurry to show something.
- final String packageName = "com.android.contacts";
- final RemoteViews remoteViews = new RemoteViews(packageName,
+
+ // TODO: should not create this every time?
+ final RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(),
R.layout.status_bar_ongoing_event_progress_bar);
+
final String title = mContext.getString(R.string.reading_vcard_title);
+
String totalCountString;
synchronized (this) {
totalCountString = String.valueOf(mTotalCount);
}
- final String text = mContext.getString(R.string.progress_notifier_message,
+ final String description = mContext.getString(R.string.progress_notifier_message,
String.valueOf(mCurrentCount),
totalCountString,
contactStruct.getDisplayName());
- // TODO: uploading image does not work correctly. (looks like a static image).
- remoteViews.setTextViewText(R.id.description, text);
+ remoteViews.setTextViewText(R.id.title, title);
+ remoteViews.setTextViewText(R.id.description, description);
remoteViews.setProgressBar(R.id.progress_bar, mTotalCount, mCurrentCount,
mTotalCount == -1);
final String percentage =
mContext.getString(R.string.percentage,
String.valueOf(mCurrentCount * 100/mTotalCount));
+
remoteViews.setTextViewText(R.id.progress_text, percentage);
remoteViews.setImageViewResource(R.id.appIcon, android.R.drawable.stat_sys_download);
final Notification notification = new Notification();
notification.icon = android.R.drawable.stat_sys_download;
notification.flags |= Notification.FLAG_ONGOING_EVENT;
+ notification.tickerText = description;
notification.contentView = remoteViews;
-
notification.contentIntent =
PendingIntent.getActivity(mContext, 0,
new Intent(mContext, ContactsListActivity.class), 0);
- mNotificationManager.notify(ImportVCardService.NOTIFICATION_ID, notification);
+ mNotificationManager.notify(VCardService.IMPORT_NOTIFICATION_ID, notification);
}
public synchronized void addTotalCount(int additionalCount) {
diff --git a/src/com/android/contacts/vcard/ImportRequest.java b/src/com/android/contacts/vcard/ImportRequest.java
index 841a09c..5d46166 100644
--- a/src/com/android/contacts/vcard/ImportRequest.java
+++ b/src/com/android/contacts/vcard/ImportRequest.java
@@ -21,9 +21,9 @@
import com.android.vcard.VCardSourceDetector;
/**
- * Class representing one request for reading vCard (as a Uri representation).
+ * Class representing one request for importing vCard (given as a Uri).
*
- * Mainly used when {@link ImportVCardActivity} requests {@link ImportVCardService}
+ * Mainly used when {@link ImportVCardActivity} requests {@link VCardService}
* to import some specific Uri.
*
* Note: This object's accepting only One Uri does NOT mean that
diff --git a/src/com/android/contacts/vcard/ImportVCardActivity.java b/src/com/android/contacts/vcard/ImportVCardActivity.java
index c7606eb..ec63a96 100644
--- a/src/com/android/contacts/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ImportVCardActivity.java
@@ -32,7 +32,6 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
-import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
@@ -100,9 +99,6 @@
/* package */ final static int VCARD_VERSION_V21 = 1;
/* package */ final static int VCARD_VERSION_V30 = 2;
- // Run on the UI thread. Must not be null except after onDestroy().
- private Handler mHandler = new Handler();
-
private AccountSelectionUtil.AccountSelectedListener mAccountSelectionListener;
private Account mAccount;
@@ -133,7 +129,7 @@
public void doBindService() {
bindService(new Intent(ImportVCardActivity.this,
- ImportVCardService.class), this, Context.BIND_AUTO_CREATE);
+ VCardService.class), this, Context.BIND_AUTO_CREATE);
}
public void setNeedFinish() {
@@ -154,14 +150,14 @@
}
}
- private void sendMessage(final ImportRequest parameter) {
+ private void sendMessage(final ImportRequest request) {
try {
mMessenger.send(Message.obtain(null,
- ImportVCardService.MSG_IMPORT_REQUEST,
- parameter));
+ VCardService.MSG_IMPORT_REQUEST,
+ request));
} catch (RemoteException e) {
- Log.e(LOG_TAG, "RemoteException is thrown when trying to import vCard");
- runOnUIThread(new DialogDisplayer(
+ Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
+ runOnUiThread(new DialogDisplayer(
getString(R.string.fail_reason_unknown)));
}
}
@@ -255,10 +251,10 @@
/**
* Caches all vCard data into local data directory so that we allow
- * {@link ImportVCardService} to access all the contents in given Uris, some of
+ * {@link VCardService} to access all the contents in given Uris, some of
* which may not be accessible from other components due to permission problem.
* (Activity which gives the Uri may allow only this Activity to access that content,
- * not the ohter components like {@link ImportVCardService}.
+ * not the other components like {@link VCardService}.
*
* We also allow the Service to happen to exit during the vCard import procedure.
*/
@@ -333,14 +329,16 @@
// smaller memory than we usually expect.
System.gc();
needFinish = false;
+
+ // TODO: call this from connection object.
unbindService(mConnection);
- runOnUIThread(new DialogDisplayer(
+ runOnUiThread(new DialogDisplayer(
getString(R.string.fail_reason_low_memory_during_import)));
} catch (IOException e) {
Log.e(LOG_TAG, e.getMessage());
needFinish = false;
unbindService(mConnection);
- runOnUIThread(new DialogDisplayer(
+ runOnUiThread(new DialogDisplayer(
getString(R.string.fail_reason_io_error)));
} finally {
mWakeLock.release();
@@ -631,14 +629,14 @@
mProgressDialogForScanVCard = null;
if (mGotIOException) {
- runOnUIThread(new DialogDisplayer(R.id.dialog_io_exception));
+ runOnUiThread(new DialogDisplayer(R.id.dialog_io_exception));
} else if (mCanceled) {
finish();
} else {
int size = mAllVCardFileList.size();
final Context context = ImportVCardActivity.this;
if (size == 0) {
- runOnUIThread(new DialogDisplayer(R.id.dialog_vcard_not_found));
+ runOnUiThread(new DialogDisplayer(R.id.dialog_vcard_not_found));
} else {
startVCardSelectAndImport();
}
@@ -697,9 +695,9 @@
size == 1) {
importVCardFromSDCard(mAllVCardFileList);
} else if (getResources().getBoolean(R.bool.config_allow_users_select_all_vcard_import)) {
- runOnUIThread(new DialogDisplayer(R.id.dialog_select_import_type));
+ runOnUiThread(new DialogDisplayer(R.id.dialog_select_import_type));
} else {
- runOnUIThread(new DialogDisplayer(R.id.dialog_select_one_vcard));
+ runOnUiThread(new DialogDisplayer(R.id.dialog_select_one_vcard));
}
}
@@ -732,7 +730,7 @@
}
private void importVCard(final Uri[] uris) {
- runOnUIThread(new Runnable() {
+ runOnUiThread(new Runnable() {
public void run() {
mVCardCacheThread = new VCardCacheThread(uris);
showDialog(R.id.dialog_cache_vcard);
@@ -962,33 +960,6 @@
}
}
- @Override
- protected void onDestroy() {
- // The code assumes the handler runs on the UI thread. If not,
- // clearing the message queue is not enough, one would have to
- // make sure that the handler does not run any callback when
- // this activity isFinishing().
-
- // Callbacks messages have what == 0.
- if (mHandler.hasMessages(0)) {
- mHandler.removeMessages(0);
- }
-
- mHandler = null; // Prevents memory leaks by breaking any circular dependency.
- super.onDestroy();
- }
-
- /**
- * Tries to run a given Runnable object when the UI thread can. Ignore it otherwise
- */
- private void runOnUIThread(Runnable runnable) {
- if (mHandler == null) {
- Log.w(LOG_TAG, "Handler object is null. No dialog is shown.");
- } else {
- mHandler.post(runnable);
- }
- }
-
/**
* Scans vCard in external storage (typically SDCard) and tries to import it.
* - When there's no SDCard available, an error dialog is shown.
diff --git a/src/com/android/contacts/vcard/ThreadStarter.java b/src/com/android/contacts/vcard/ThreadStarter.java
new file mode 100644
index 0000000..d7adad6
--- /dev/null
+++ b/src/com/android/contacts/vcard/ThreadStarter.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.vcard;
+
+public interface ThreadStarter {
+ public void start();
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/vcard/ImportVCardService.java b/src/com/android/contacts/vcard/VCardService.java
similarity index 69%
rename from src/com/android/contacts/vcard/ImportVCardService.java
rename to src/com/android/contacts/vcard/VCardService.java
index 76f597c..58e1333 100644
--- a/src/com/android/contacts/vcard/ImportVCardService.java
+++ b/src/com/android/contacts/vcard/VCardService.java
@@ -29,38 +29,51 @@
/**
* The class responsible for importing vCard from one ore multiple Uris.
*/
-public class ImportVCardService extends Service {
+public class VCardService extends Service {
private final static String LOG_TAG = "ImportVCardService";
/* package */ static final int MSG_IMPORT_REQUEST = 1;
+ /* package */ static final int MSG_EXPORT_REQUEST = 2;
- /* package */ static final int NOTIFICATION_ID = 1000;
+ /* package */ static final int IMPORT_NOTIFICATION_ID = 1000;
+ /* package */ static final int EXPORT_NOTIFICATION_ID = 1001;
/**
* Small vCard file is imported soon, so any meassage saying "vCard import started" is
* not needed. We show the message when the size of vCard is larger than this constant.
*/
- private static final int IMPORT_NOTIFICATION_THRESHOLD = 10;
+ private static final int IMPORT_NOTIFICATION_THRESHOLD = 10;
public class ImportRequestHandler extends Handler {
- private final ImportRequestProcessor mRequestProcessor =
- new ImportRequestProcessor(ImportVCardService.this);
+ private final ImportProcessor mImportProcessor =
+ new ImportProcessor(VCardService.this);
+ private final ExportProcessor mExportProcessor =
+ new ExportProcessor(VCardService.this);
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_IMPORT_REQUEST: {
final ImportRequest parameter = (ImportRequest)msg.obj;
+ mImportProcessor.pushRequest(parameter);
if (parameter.entryCount > IMPORT_NOTIFICATION_THRESHOLD) {
- Toast.makeText(ImportVCardService.this,
+ Toast.makeText(VCardService.this,
getString(R.string.vcard_importer_start_message),
Toast.LENGTH_LONG).show();
}
- mRequestProcessor.pushRequest(parameter);
break;
}
- default:
+ case MSG_EXPORT_REQUEST: {
+ final ExportRequest parameter = (ExportRequest)msg.obj;
+ mExportProcessor.pushRequest(parameter);
+ Toast.makeText(VCardService.this,
+ getString(R.string.vcard_exporter_start_message),
+ Toast.LENGTH_LONG).show();
+ break;
+ }
+ default: {
Log.e(LOG_TAG, "Unknown request type: " + msg.what);
super.hasMessages(msg.what);
+ }
}
}
}
diff --git a/src/com/android/contacts/views/ContactLoader.java b/src/com/android/contacts/views/ContactLoader.java
index f03d269..87c3a34 100644
--- a/src/com/android/contacts/views/ContactLoader.java
+++ b/src/com/android/contacts/views/ContactLoader.java
@@ -458,6 +458,7 @@
mContact = result;
mLookupUri = result.getLookupUri();
if (result != null) {
+ unregisterObserver();
if (mObserver == null) {
mObserver = new ForceLoadContentObserver();
}
@@ -472,6 +473,13 @@
}
}
+ private void unregisterObserver() {
+ if (mObserver != null) {
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ mObserver = null;
+ }
+ }
+
public ContactLoader(Context context, Uri lookupUri) {
super(context);
mLookupUri = lookupUri;
@@ -495,9 +503,6 @@
@Override
public void stopLoading() {
mContact = null;
- if (mObserver != null) {
- getContext().getContentResolver().unregisterContentObserver(mObserver);
- }
}
@Override
diff --git a/src/com/android/contacts/views/detail/ContactDetailFragment.java b/src/com/android/contacts/views/detail/ContactDetailFragment.java
index bfbd4d7..49471cc 100644
--- a/src/com/android/contacts/views/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/views/detail/ContactDetailFragment.java
@@ -103,6 +103,8 @@
private Uri mLookupUri;
private Listener mListener;
+ private boolean mIsInitialized;
+
private ContactLoader.Result mContactData;
private ContactDetailHeaderView mHeaderView;
private ListView mListView;
@@ -194,11 +196,13 @@
public void loadUri(Uri lookupUri) {
mLookupUri = lookupUri;
- startLoading(LOADER_DETAILS, null);
+ if (mIsInitialized) startLoading(LOADER_DETAILS, null);
}
@Override
protected void onInitializeLoaders() {
+ mIsInitialized = true;
+ if (mLookupUri != null) startLoading(LOADER_DETAILS, null);
}
@Override
diff --git a/src/com/android/contacts/views/editor/ContactEditorFragment.java b/src/com/android/contacts/views/editor/ContactEditorFragment.java
index 4f57fb7..58f56ff 100644
--- a/src/com/android/contacts/views/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/views/editor/ContactEditorFragment.java
@@ -23,13 +23,14 @@
import com.android.contacts.model.Sources;
import com.android.contacts.model.ContactsSource.DataKind;
import com.android.contacts.ui.EditContactActivity;
-import com.android.contacts.util.Constants;
import com.android.contacts.util.DataStatus;
import com.android.contacts.views.ContactLoader;
-import com.android.contacts.views.editor.typeViews.DataView;
-import com.android.contacts.views.editor.typeViews.FooterView;
-import com.android.contacts.views.editor.typeViews.HeaderView;
-import com.android.contacts.views.editor.typeViews.PhotoView;
+import com.android.contacts.views.editor.view.ViewTypes;
+import com.android.contacts.views.editor.viewModel.BaseViewModel;
+import com.android.contacts.views.editor.viewModel.DataViewModel;
+import com.android.contacts.views.editor.viewModel.EmailViewModel;
+import com.android.contacts.views.editor.viewModel.FooterViewModel;
+import com.android.contacts.views.editor.viewModel.PhoneViewModel;
import android.app.Activity;
import android.app.AlertDialog;
@@ -44,8 +45,6 @@
import android.content.Intent;
import android.content.Loader;
import android.content.Entity.NamedContentValues;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract.CommonDataKinds;
@@ -62,7 +61,6 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
@@ -78,12 +76,11 @@
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.Toast;
-import android.widget.AdapterView.OnItemClickListener;
import java.util.ArrayList;
public class ContactEditorFragment extends LoaderManagingFragment<ContactLoader.Result>
- implements OnCreateContextMenuListener, OnItemClickListener {
+ implements OnCreateContextMenuListener {
private static final String TAG = "ContactEditorFragment";
private static final String BUNDLE_RAW_CONTACT_ID = "rawContactId";
@@ -96,9 +93,11 @@
private Uri mLookupUri;
private Listener mListener;
+ private boolean mIsInitialized;
+
private ContactLoader.Result mContactData;
private ContactEditorHeaderView mHeaderView;
- private ListView mListView;
+ private MyListView mListView;
private ViewAdapter mAdapter;
private int mReadOnlySourcesCnt;
@@ -108,7 +107,7 @@
/**
* A list of RawContacts included in this Contact.
*/
- private ArrayList<RawContact> mRawContacts = new ArrayList<RawContact>();
+ private ArrayList<DisplayRawContact> mRawContacts = new ArrayList<DisplayRawContact>();
private LayoutInflater mInflater;
@@ -130,13 +129,13 @@
mInflater = inflater;
- mHeaderView =
- (ContactEditorHeaderView) view.findViewById(R.id.contact_header_widget);
+ mHeaderView = (ContactEditorHeaderView) view.findViewById(R.id.contact_header_widget);
- mListView = (ListView) view.findViewById(android.R.id.list);
+ mListView = (MyListView) view.findViewById(android.R.id.list);
mListView.setOnCreateContextMenuListener(this);
mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
mListView.setOnItemClickListener(this);
+ mListView.setItemsCanFocus(true);
return view;
}
@@ -147,11 +146,13 @@
public void loadUri(Uri lookupUri) {
mLookupUri = lookupUri;
- startLoading(LOADER_DETAILS, null);
+ if (mIsInitialized) startLoading(LOADER_DETAILS, null);
}
@Override
protected void onInitializeLoaders() {
+ mIsInitialized = true;
+ if (mLookupUri != null) startLoading(LOADER_DETAILS, null);
}
@Override
@@ -248,8 +249,8 @@
mReadOnlySourcesCnt += 1;
}
- final RawContact rawContact =
- new RawContact(contactsSource, accountName, rawContactId, writable);
+ final DisplayRawContact rawContact = new DisplayRawContact(mContext, contactsSource,
+ accountName, rawContactId, writable, mRawContactFooterListener);
mRawContacts.add(rawContact);
for (NamedContentValues subValue : entity.getSubValues()) {
@@ -264,7 +265,22 @@
ContactsSource.LEVEL_MIMETYPES);
if (kind == null) continue;
- final DataViewEntry entry = new DataViewEntry(mContext, mimeType, kind,
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ final PhoneViewModel itemEditor = PhoneViewModel.createForExisting(mContext,
+ rawContact, dataId, entryValues, kind.titleRes);
+ rawContact.getFields().add(itemEditor);
+ continue;
+ }
+
+ if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ final EmailViewModel itemEditor = EmailViewModel.createForExisting(mContext,
+ rawContact, dataId, entryValues, kind.titleRes);
+ rawContact.getFields().add(itemEditor);
+ continue;
+ }
+
+
+ final DataViewModel entry = new DataViewModel(mContext, mimeType, kind,
rawContact, dataId, entryValues);
final boolean hasData = !TextUtils.isEmpty(entry.data);
@@ -332,223 +348,12 @@
}
}
- private static String buildActionString(DataKind kind, ContentValues values,
- boolean lowerCase, Context context) {
- if (kind.actionHeader == null) {
- return null;
- }
- CharSequence actionHeader = kind.actionHeader.inflateUsing(context, values);
- if (actionHeader == null) {
- return null;
- }
- return lowerCase ? actionHeader.toString().toLowerCase() : actionHeader.toString();
- }
-
- private static String buildDataString(DataKind kind, ContentValues values,
- Context context) {
- if (kind.actionBody == null) {
- return null;
- }
- CharSequence actionBody = kind.actionBody.inflateUsing(context, values);
- return actionBody == null ? null : actionBody.toString();
- }
-
- private abstract static class BaseViewEntry {
- private final RawContact mRawContact;
-
- public BaseViewEntry(RawContact rawContact) {
- mRawContact = rawContact;
- }
-
- public RawContact getRawContact() {
- return mRawContact;
- }
-
- public abstract int getEntryType();
- public abstract View getView(View convertView, ViewGroup parent);
- }
-
- private class HeaderViewEntry extends BaseViewEntry {
- private boolean mCollapsed;
-
- public HeaderViewEntry(RawContact rawContact) {
- super(rawContact);
- }
-
- public boolean isCollapsed() {
- return mCollapsed;
- }
-
- public void setCollapsed(boolean collapsed) {
- mCollapsed = collapsed;
- }
-
- @Override
- public int getEntryType() {
- return ItemTypes.RAW_CONTACT_HEADER;
- }
-
- @Override
- public View getView(View convertView, ViewGroup parent) {
- final HeaderView result = convertView != null
- ? (HeaderView) convertView
- : HeaderView.inflate(mInflater, parent, false);
-
- CharSequence accountType = getRawContact().getSource().getDisplayLabel(mContext);
- if (TextUtils.isEmpty(accountType)) {
- accountType = mContext.getString(R.string.account_phone);
- }
- final String accountName = getRawContact().getAccountName();
-
- final String accountTypeDisplay;
- if (TextUtils.isEmpty(accountName)) {
- accountTypeDisplay = mContext.getString(R.string.account_type_format,
- accountType);
- } else {
- accountTypeDisplay = mContext.getString(R.string.account_type_and_name,
- accountType, accountName);
- }
-
- result.setCaptionText(accountTypeDisplay);
- result.setLogo(getRawContact().getSource().getDisplayIcon(mContext));
-
- return result;
- }
- }
-
- public class FooterViewEntry extends BaseViewEntry {
- public FooterViewEntry(RawContact rawContact) {
- super(rawContact);
- }
-
- @Override
- public int getEntryType() {
- return ItemTypes.RAW_CONTACT_FOOTER;
- }
-
- @Override
- public View getView(View convertView, ViewGroup parent) {
- final FooterView result = convertView != null
- ? (FooterView) convertView
- : FooterView.inflate(mInflater, parent, false);
-
- result.setListener(mViewListener);
- return result;
- }
-
- private FooterView.Listener mViewListener = new FooterView.Listener() {
- public void onAddClicked() {
- // Create a bundle to show the Dialog
- final Bundle bundle = new Bundle();
- bundle.putLong(BUNDLE_RAW_CONTACT_ID, getRawContact().getId());
- if (mListener != null) {
- mListener.onDialogRequested(R.id.edit_dialog_add_information, bundle);
- }
- }
- public void onSeparateClicked() {
- }
- public void onDeleteClicked() {
- }
- };
- }
-
- private class DataViewEntry extends BaseViewEntry {
- public String label;
- public String data;
- public Uri uri;
- public long id = 0;
- public int maxLines = 1;
- public String mimetype;
-
- public int actionIcon = -1;
- public boolean isPrimary = false;
- public Intent intent;
- public Intent secondaryIntent = null;
- public int maxLabelLines = 1;
- public byte[] binaryData = null;
-
- /**
- * Build new {@link DataViewEntry} and populate from the given values.
- */
- public DataViewEntry(Context context, String mimeType, DataKind kind,
- RawContact rawContact, long dataId, ContentValues values) {
- super(rawContact);
- id = dataId;
- uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
- mimetype = mimeType;
- label = buildActionString(kind, values, false, context);
- data = buildDataString(kind, values, context);
- binaryData = values.getAsByteArray(Data.DATA15);
- }
-
- @Override
- public int getEntryType() {
- return Photo.CONTENT_ITEM_TYPE.equals(mimetype) ? ItemTypes.PHOTO : ItemTypes.DATA;
- }
-
- @Override
- public View getView(View convertView, ViewGroup parent) {
- // Special Case: Photo
- if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) {
- final PhotoView result = convertView != null
- ? (PhotoView) convertView
- : PhotoView.inflate(mInflater, parent, false);
-
- final Bitmap bitmap = binaryData != null
- ? BitmapFactory.decodeByteArray(binaryData, 0, binaryData.length)
- : null;
- result.setPhoto(bitmap);
- return result;
- }
-
- // All other cases
- final DataView result = convertView != null
- ? (DataView) convertView
- : DataView.inflate(mInflater, parent, false);
-
- // Set the label
- result.setLabelText(label, maxLabelLines);
-
- // Set data
- if (data != null) {
- if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)
- || Constants.MIME_SMS_ADDRESS.equals(mimetype)) {
- result.setDataText(PhoneNumberUtils.formatNumber(data), maxLines);
- } else {
- result.setDataText(data, maxLines);
- }
- } else {
- result.setDataText("", maxLines);
- }
-
- // Set the primary icon
- result.setPrimary(isPrimary);
-
- // Set the action icon
- result.setPrimaryIntent(intent, mContext.getResources(), actionIcon);
-
- // Set the secondary action button
- // TODO: Change this to our new form
- result.setSecondaryIntent(null, null, 0);
- return result;
- }
- }
-
- /** Possible Item Types */
- private interface ItemTypes {
- public static final int DATA = 0;
- public static final int PHOTO = 1;
- public static final int RAW_CONTACT_HEADER = 2;
- public static final int RAW_CONTACT_FOOTER = 3;
- public static final int _COUNT = 4;
- }
-
private final class ViewAdapter extends BaseAdapter {
public View getView(int position, View convertView, ViewGroup parent) {
final View result;
- final BaseViewEntry viewEntry = getEntry(position);
- return viewEntry.getView(convertView, parent);
+ final BaseViewModel viewEntry = getEntry(position);
+ return viewEntry.getView(mInflater, convertView, parent);
}
public Object getItem(int position) {
@@ -560,9 +365,9 @@
return position;
}
- private BaseViewEntry getEntry(int position) {
+ private BaseViewModel getEntry(int position) {
for (int i = 0; i < mRawContacts.size(); i++) {
- final RawContact rawContact = mRawContacts.get(i);
+ final DisplayRawContact rawContact = mRawContacts.get(i);
if (position == 0) return rawContact.getHeader();
// Collapsed header? Count one item and continue
@@ -571,7 +376,7 @@
continue;
}
- final ArrayList<DataViewEntry> fields = rawContact.getFields();
+ final ArrayList<BaseViewModel> fields = rawContact.getFields();
// +1 for header, +1 for footer
final int fieldCount = fields.size() + 2;
if (position == fieldCount - 1) {
@@ -588,7 +393,7 @@
@Override
public int getViewTypeCount() {
- return ItemTypes._COUNT;
+ return ViewTypes._COUNT;
}
@Override
@@ -599,7 +404,7 @@
public int getCount() {
int result = 0;
for (int i = 0; i < mRawContacts.size(); i++) {
- final RawContact rawContact = mRawContacts.get(i);
+ final DisplayRawContact rawContact = mRawContacts.get(i);
if (rawContact.getHeader().isCollapsed()) {
// just one header item
result++;
@@ -612,14 +417,17 @@
}
}
+ @Override
public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.view, menu);
}
+ @Override
public void onPrepareOptionsMenu(Menu menu) {
// TODO: Prepare options
}
+ @Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_edit: {
@@ -627,7 +435,7 @@
// later
final Intent intent = new Intent();
intent.setClass(mContext, EditContactActivity.class);
- final long rawContactId = mRawContacts.get(0).mId;
+ final long rawContactId = mRawContacts.get(0).getId();
final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
rawContactId);
intent.setAction(Intent.ACTION_EDIT);
@@ -684,6 +492,7 @@
if (mListener != null) mListener.onDialogRequested(dialogId, null);
}
+ @Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
AdapterView.AdapterContextMenuInfo info;
try {
@@ -699,9 +508,9 @@
return;
}
- final BaseViewEntry baseEntry = mAdapter.getEntry(info.position);
- if (baseEntry instanceof DataViewEntry) {
- final DataViewEntry entry = (DataViewEntry) baseEntry;
+ final BaseViewModel baseEntry = mAdapter.getEntry(info.position);
+ if (baseEntry instanceof DataViewModel) {
+ final DataViewModel entry = (DataViewModel) baseEntry;
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);
@@ -720,23 +529,19 @@
}
}
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (mListener == null) return;
- final BaseViewEntry baseEntry = mAdapter.getEntry(position);
- if (baseEntry == null) return;
-
- if (baseEntry instanceof HeaderViewEntry) {
- // Toggle rawcontact visibility
- final HeaderViewEntry entry = (HeaderViewEntry) baseEntry;
- entry.setCollapsed(!entry.isCollapsed());
- mAdapter.notifyDataSetChanged();
- } else if (baseEntry instanceof DataViewEntry) {
- final DataViewEntry entry = (DataViewEntry) baseEntry;
- final Intent intent = entry.intent;
- if (intent == null) return;
- mListener.onEditorRequested(intent);
- }
- }
+ // This was the ListView based code to expand/collapse sections.
+// public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+// if (mListener == null) return;
+// final BaseViewModel baseEntry = mAdapter.getEntry(position);
+// if (baseEntry == null) return;
+//
+// if (baseEntry instanceof HeaderViewModel) {
+// // Toggle rawcontact visibility
+// final HeaderViewModel entry = (HeaderViewModel) baseEntry;
+// entry.setCollapsed(!entry.isCollapsed());
+// mAdapter.notifyDataSetChanged();
+// }
+// }
private final DialogInterface.OnClickListener mDeleteListener =
new DialogInterface.OnClickListener() {
@@ -787,7 +592,7 @@
}
case R.id.edit_dialog_add_information: {
final long rawContactId = bundle.getLong(BUNDLE_RAW_CONTACT_ID);
- final RawContact rawContact = findRawContactById(rawContactId);
+ final DisplayRawContact rawContact = findRawContactById(rawContactId);
if (rawContact == null) return null;
final ContactsSource source = rawContact.getSource();
@@ -831,13 +636,14 @@
}
}
- private RawContact findRawContactById(long rawContactId) {
- for (RawContact rawContact : mRawContacts) {
+ private DisplayRawContact findRawContactById(long rawContactId) {
+ for (DisplayRawContact rawContact : mRawContacts) {
if (rawContact.getId() == rawContactId) return rawContact;
}
return null;
}
+ @Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ITEM_MAKE_DEFAULT: {
@@ -852,11 +658,11 @@
}
private boolean makeItemDefault(MenuItem item) {
- final BaseViewEntry baseEntry = getViewEntryForMenuItem(item);
- if (baseEntry == null || !(baseEntry instanceof DataViewEntry)) {
+ final BaseViewModel baseEntry = getViewEntryForMenuItem(item);
+ if (baseEntry == null || !(baseEntry instanceof DataViewModel)) {
return false;
}
- final DataViewEntry entry = (DataViewEntry) baseEntry;
+ final DataViewModel entry = (DataViewModel) baseEntry;
// Update the primary values in the data record.
ContentValues values = new ContentValues(1);
@@ -867,7 +673,7 @@
return true;
}
- private BaseViewEntry getViewEntryForMenuItem(MenuItem item) {
+ private BaseViewModel getViewEntryForMenuItem(MenuItem item) {
final AdapterView.AdapterContextMenuInfo info;
try {
info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
@@ -879,50 +685,21 @@
return mAdapter.getEntry(info.position);
}
- private class RawContact {
- private final ContactsSource mSource;
- private String mAccountName;
- private final long mId;
- private boolean mWritable;
- private final HeaderViewEntry mHeader = new HeaderViewEntry(this);
- private final FooterViewEntry mFooter = new FooterViewEntry(this);
- private final ArrayList<DataViewEntry> mFields = new ArrayList<DataViewEntry>();
-
- public RawContact(ContactsSource source, String accountName, long id, boolean writable) {
- mSource = source;
- mAccountName = accountName;
- mId = id;
- mWritable = writable;
+ private FooterViewModel.Listener mRawContactFooterListener =
+ new FooterViewModel.Listener() {
+ public void onAddClicked(DisplayRawContact rawContact) {
+ // Create a bundle to show the Dialog
+ final Bundle bundle = new Bundle();
+ bundle.putLong(BUNDLE_RAW_CONTACT_ID, rawContact.getId());
+ if (mListener != null) {
+ mListener.onDialogRequested(R.id.edit_dialog_add_information, bundle);
+ }
}
-
- public ContactsSource getSource() {
- return mSource;
+ public void onSeparateClicked(DisplayRawContact rawContact) {
}
-
- public String getAccountName() {
- return mAccountName;
+ public void onDeleteClicked(DisplayRawContact rawContact) {
}
-
- public long getId() {
- return mId;
- }
-
- public boolean isWritable() {
- return mWritable;
- }
-
- public ArrayList<DataViewEntry> getFields() {
- return mFields;
- }
-
- public HeaderViewEntry getHeader() {
- return mHeader;
- }
-
- public FooterViewEntry getFooter() {
- return mFooter;
- }
- }
+ };
public static interface Listener {
/**
diff --git a/src/com/android/contacts/views/editor/DisplayRawContact.java b/src/com/android/contacts/views/editor/DisplayRawContact.java
new file mode 100644
index 0000000..0e6564b
--- /dev/null
+++ b/src/com/android/contacts/views/editor/DisplayRawContact.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.views.editor;
+
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.views.editor.viewModel.BaseViewModel;
+import com.android.contacts.views.editor.viewModel.FooterViewModel;
+import com.android.contacts.views.editor.viewModel.HeaderViewModel;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+
+public class DisplayRawContact {
+ private final ContactsSource mSource;
+ private String mAccountName;
+ private final long mId;
+ private boolean mWritable;
+ private final HeaderViewModel mHeader;
+ private final FooterViewModel mFooter;
+ private final ArrayList<BaseViewModel> mFields = new ArrayList<BaseViewModel>();
+
+ public DisplayRawContact(Context context, ContactsSource source, String accountName, long id,
+ boolean writable, FooterViewModel.Listener footerListener) {
+ mSource = source;
+ mAccountName = accountName;
+ mId = id;
+ mWritable = writable;
+ mHeader = new HeaderViewModel(context, this);
+ mFooter = new FooterViewModel(context, this, footerListener);
+ }
+
+ public ContactsSource getSource() {
+ return mSource;
+ }
+
+ public String getAccountName() {
+ return mAccountName;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public boolean isWritable() {
+ return mWritable;
+ }
+
+ public ArrayList<BaseViewModel> getFields() {
+ return mFields;
+ }
+
+ public HeaderViewModel getHeader() {
+ return mHeader;
+ }
+
+ public FooterViewModel getFooter() {
+ return mFooter;
+ }
+}
diff --git a/src/com/android/contacts/views/editor/MyListView.java b/src/com/android/contacts/views/editor/MyListView.java
new file mode 100644
index 0000000..12651c1
--- /dev/null
+++ b/src/com/android/contacts/views/editor/MyListView.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.views.editor;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
+
+/**
+ * Compatibility pseudo-ListView. This just renders everything into a LinearLayout using a ListView
+ * adapter. If this turns out to be fast enough, we can keep using this. This view will be removed
+ * once the decision has been made
+ */
+public class MyListView extends LinearLayout {
+ public MyListView(Context context) {
+ super(context);
+ }
+
+ public MyListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ setOrientation(VERTICAL);
+ }
+
+ public void setOnItemClickListener(ContactEditorFragment contactEditorFragment) {
+ // Keep compatibility with ListView
+ }
+
+ public void setItemsCanFocus(boolean value) {
+ // Keep compatibility with ListView
+ }
+
+ public void setAdapter(BaseAdapter adapter) {
+ removeAllViews();
+ for (int i = 0; i < adapter.getCount(); i++) {
+ final View childView = adapter.getView(i, null, this);
+ addView(childView);
+ }
+ }
+}
diff --git a/src/com/android/contacts/views/editor/typeViews/DataView.java b/src/com/android/contacts/views/editor/view/DataView.java
similarity index 97%
rename from src/com/android/contacts/views/editor/typeViews/DataView.java
rename to src/com/android/contacts/views/editor/view/DataView.java
index f146e36..e4cd1de 100644
--- a/src/com/android/contacts/views/editor/typeViews/DataView.java
+++ b/src/com/android/contacts/views/editor/view/DataView.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.contacts.views.editor.typeViews;
+package com.android.contacts.views.editor.view;
import com.android.contacts.R;
@@ -52,7 +52,8 @@
public static DataView inflate(LayoutInflater inflater, ViewGroup parent,
boolean attachToRoot) {
- return (DataView) inflater.inflate(R.layout.list_edit_item_text_icons, parent, attachToRoot);
+ return (DataView) inflater.inflate(R.layout.list_edit_item_text_icons, parent,
+ attachToRoot);
}
@Override
diff --git a/src/com/android/contacts/views/editor/view/FieldAndTypeView.java b/src/com/android/contacts/views/editor/view/FieldAndTypeView.java
new file mode 100644
index 0000000..69e35b3
--- /dev/null
+++ b/src/com/android/contacts/views/editor/view/FieldAndTypeView.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.views.editor.view;
+
+import com.android.contacts.R;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class FieldAndTypeView extends LinearLayout {
+ private TextView mCaptionTextView;
+ private EditText mFieldEditText;
+ private Button mTypeButton;
+ private Listener mListener;
+ private boolean mHasFocus;
+
+ public FieldAndTypeView(Context context) {
+ super(context);
+ }
+
+ public FieldAndTypeView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public FieldAndTypeView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public static FieldAndTypeView inflate(LayoutInflater inflater, ViewGroup parent,
+ boolean attachToRoot) {
+ return (FieldAndTypeView) inflater.inflate(R.layout.list_edit_item_field_and_type,
+ parent, attachToRoot);
+ }
+
+ public void setListener(Listener value) {
+ mListener = value;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mCaptionTextView = (TextView) findViewById(R.id.caption);
+ mFieldEditText = (EditText) findViewById(R.id.field);
+ mFieldEditText.setOnFocusChangeListener(mFieldEditTextFocusChangeListener);
+ mTypeButton = (Button) findViewById(R.id.type);
+ }
+
+ public void setLabelText(int resId) {
+ mCaptionTextView.setText(resId);
+ }
+
+ public void setFieldValue(CharSequence value) {
+ mFieldEditText.setText(value);
+ }
+
+ public CharSequence getFieldValue() {
+ return mFieldEditText.getText();
+ }
+
+ public void setTypeDisplayLabel(CharSequence type) {
+ mTypeButton.setText(type);
+ }
+
+ private OnFocusChangeListener mFieldEditTextFocusChangeListener = new OnFocusChangeListener() {
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (mHasFocus && !hasFocus && mListener != null) {
+ mListener.onFocusLost(FieldAndTypeView.this);
+ }
+ mHasFocus = hasFocus;
+ }
+ };
+
+ public interface Listener {
+ void onFocusLost(FieldAndTypeView view);
+ }
+}
diff --git a/src/com/android/contacts/views/editor/typeViews/FooterView.java b/src/com/android/contacts/views/editor/view/FooterView.java
similarity index 97%
rename from src/com/android/contacts/views/editor/typeViews/FooterView.java
rename to src/com/android/contacts/views/editor/view/FooterView.java
index d8ae66d..adc47cd 100644
--- a/src/com/android/contacts/views/editor/typeViews/FooterView.java
+++ b/src/com/android/contacts/views/editor/view/FooterView.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.contacts.views.editor.typeViews;
+package com.android.contacts.views.editor.view;
import com.android.contacts.R;
diff --git a/src/com/android/contacts/views/editor/typeViews/HeaderView.java b/src/com/android/contacts/views/editor/view/HeaderView.java
similarity index 97%
rename from src/com/android/contacts/views/editor/typeViews/HeaderView.java
rename to src/com/android/contacts/views/editor/view/HeaderView.java
index bd69c50..6822520 100644
--- a/src/com/android/contacts/views/editor/typeViews/HeaderView.java
+++ b/src/com/android/contacts/views/editor/view/HeaderView.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.contacts.views.editor.typeViews;
+package com.android.contacts.views.editor.view;
import com.android.contacts.R;
diff --git a/src/com/android/contacts/views/editor/typeViews/PhotoView.java b/src/com/android/contacts/views/editor/view/PhotoView.java
similarity index 97%
rename from src/com/android/contacts/views/editor/typeViews/PhotoView.java
rename to src/com/android/contacts/views/editor/view/PhotoView.java
index 9e63a79..d43e90e 100644
--- a/src/com/android/contacts/views/editor/typeViews/PhotoView.java
+++ b/src/com/android/contacts/views/editor/view/PhotoView.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.contacts.views.editor.typeViews;
+package com.android.contacts.views.editor.view;
import com.android.contacts.R;
diff --git a/src/com/android/contacts/views/editor/view/ViewTypes.java b/src/com/android/contacts/views/editor/view/ViewTypes.java
new file mode 100644
index 0000000..7cd2702
--- /dev/null
+++ b/src/com/android/contacts/views/editor/view/ViewTypes.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.views.editor.view;
+
+public interface ViewTypes {
+ public static final int DATA = 0;
+ public static final int FIELD_AND_TYPE = 1;
+ public static final int PHOTO = 2;
+ public static final int RAW_CONTACT_HEADER = 3;
+ public static final int RAW_CONTACT_FOOTER = 4;
+ public static final int _COUNT = 5;
+}
diff --git a/src/com/android/contacts/views/editor/viewModel/BaseViewModel.java b/src/com/android/contacts/views/editor/viewModel/BaseViewModel.java
new file mode 100644
index 0000000..0a324ca
--- /dev/null
+++ b/src/com/android/contacts/views/editor/viewModel/BaseViewModel.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.views.editor.viewModel;
+
+import com.android.contacts.views.editor.DisplayRawContact;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public abstract class BaseViewModel {
+ // TODO: Consider just storing the Id of the RawContact. Would prevent some cyclic references
+ private final DisplayRawContact mRawContact;
+ private final Context mContext;
+
+ public BaseViewModel(Context context, DisplayRawContact rawContact) {
+ if (context == null) throw new IllegalArgumentException("context must not be null");
+ if (rawContact == null) throw new IllegalArgumentException("rawContact must not be null");
+ mContext = context;
+ mRawContact = rawContact;
+ }
+
+ public DisplayRawContact getRawContact() {
+ return mRawContact;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public abstract int getEntryType();
+ public abstract View getView(LayoutInflater inflater, View convertView, ViewGroup parent);
+}
diff --git a/src/com/android/contacts/views/editor/viewModel/DataViewModel.java b/src/com/android/contacts/views/editor/viewModel/DataViewModel.java
new file mode 100644
index 0000000..fb1c4c5
--- /dev/null
+++ b/src/com/android/contacts/views/editor/viewModel/DataViewModel.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.views.editor.viewModel;
+
+import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.util.Constants;
+import com.android.contacts.views.editor.DisplayRawContact;
+import com.android.contacts.views.editor.view.DataView;
+import com.android.contacts.views.editor.view.PhotoView;
+import com.android.contacts.views.editor.view.ViewTypes;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.telephony.PhoneNumberUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class DataViewModel extends BaseViewModel {
+ public String label;
+ public String data;
+ public Uri uri;
+ public long id = 0;
+ public int maxLines = 1;
+ public String mimetype;
+
+ public int actionIcon = -1;
+ public boolean isPrimary = false;
+ public Intent intent;
+ public Intent secondaryIntent = null;
+ public int maxLabelLines = 1;
+ public byte[] binaryData = null;
+
+ /**
+ * Build new {@link DataViewModel} and populate from the given values.
+ */
+ public DataViewModel(Context context, String mimeType, DataKind kind,
+ DisplayRawContact rawContact, long dataId, ContentValues values) {
+ super(context, rawContact);
+ id = dataId;
+ uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
+ mimetype = mimeType;
+ label = buildActionString(kind, values, false, context);
+ data = buildDataString(kind, values, context);
+ binaryData = values.getAsByteArray(Data.DATA15);
+ }
+
+ @Override
+ public int getEntryType() {
+ return Photo.CONTENT_ITEM_TYPE.equals(mimetype) ? ViewTypes.PHOTO : ViewTypes.DATA;
+ }
+
+ @Override
+ public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) {
+ // Special Case: Photo
+ if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ final PhotoView result = convertView != null
+ ? (PhotoView) convertView
+ : PhotoView.inflate(inflater, parent, false);
+
+ final Bitmap bitmap = binaryData != null
+ ? BitmapFactory.decodeByteArray(binaryData, 0, binaryData.length)
+ : null;
+ result.setPhoto(bitmap);
+ return result;
+ }
+
+ // All other cases
+ final DataView result = convertView != null
+ ? (DataView) convertView
+ : DataView.inflate(inflater, parent, false);
+
+ // Set the label
+ result.setLabelText(label, maxLabelLines);
+
+ // Set data
+ if (data != null) {
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)
+ || Constants.MIME_SMS_ADDRESS.equals(mimetype)) {
+ result.setDataText(PhoneNumberUtils.formatNumber(data), maxLines);
+ } else {
+ result.setDataText(data, maxLines);
+ }
+ } else {
+ result.setDataText("", maxLines);
+ }
+
+ // Set the primary icon
+ result.setPrimary(isPrimary);
+
+ // Set the action icon
+ result.setPrimaryIntent(intent, getContext().getResources(), actionIcon);
+
+ // Set the secondary action button
+ // TODO: Change this to our new form
+ result.setSecondaryIntent(null, null, 0);
+ return result;
+ }
+
+ private static String buildActionString(DataKind kind, ContentValues values,
+ boolean lowerCase, Context context) {
+ if (kind.actionHeader == null) {
+ return null;
+ }
+ CharSequence actionHeader = kind.actionHeader.inflateUsing(context, values);
+ if (actionHeader == null) {
+ return null;
+ }
+ return lowerCase ? actionHeader.toString().toLowerCase() : actionHeader.toString();
+ }
+
+ private static String buildDataString(DataKind kind, ContentValues values,
+ Context context) {
+ if (kind.actionBody == null) {
+ return null;
+ }
+ CharSequence actionBody = kind.actionBody.inflateUsing(context, values);
+ return actionBody == null ? null : actionBody.toString();
+ }
+
+}
diff --git a/src/com/android/contacts/views/editor/viewModel/EmailViewModel.java b/src/com/android/contacts/views/editor/viewModel/EmailViewModel.java
new file mode 100644
index 0000000..70bb8ea
--- /dev/null
+++ b/src/com/android/contacts/views/editor/viewModel/EmailViewModel.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.views.editor.viewModel;
+
+import com.android.contacts.views.editor.DisplayRawContact;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+
+public class EmailViewModel extends FieldAndTypeViewModel {
+ private EmailViewModel(Context context, DisplayRawContact rawContact, long dataId,
+ ContentValues contentValues, int titleResId) {
+ super(context, rawContact, dataId, contentValues, titleResId, Email.ADDRESS, Email.TYPE,
+ Email.LABEL);
+ }
+
+ public static EmailViewModel createForExisting(Context context, DisplayRawContact rawContact,
+ long dataId, ContentValues contentValues, int titleResId) {
+ return new EmailViewModel(context, rawContact, dataId, contentValues, titleResId);
+ }
+
+ @Override
+ protected CharSequence getTypeDisplayLabel() {
+ return Email.getTypeLabel(getContext().getResources(), getType(), getLabel());
+ }
+}
diff --git a/src/com/android/contacts/views/editor/viewModel/FieldAndTypeViewModel.java b/src/com/android/contacts/views/editor/viewModel/FieldAndTypeViewModel.java
new file mode 100644
index 0000000..e6468c6
--- /dev/null
+++ b/src/com/android/contacts/views/editor/viewModel/FieldAndTypeViewModel.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.views.editor.viewModel;
+
+import com.android.contacts.views.ContactSaveService;
+import com.android.contacts.views.editor.DisplayRawContact;
+import com.android.contacts.views.editor.view.FieldAndTypeView;
+import com.android.contacts.views.editor.view.ViewTypes;
+import com.android.internal.util.ArrayUtils;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ContentProviderOperation.Builder;
+import android.net.Uri;
+import android.provider.ContactsContract.Data;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+public abstract class FieldAndTypeViewModel extends BaseViewModel {
+ private static final String TAG = "FieldAndTypeViewModel";
+
+ private final long mDataId;
+ private final ContentValues mContentValues;
+ private final int mLabelResId;
+ private final Uri mDataUri;
+ private final String mFieldColumn;
+ private final String mLabelColumn;
+ private final String mTypeColumn;
+
+ protected FieldAndTypeViewModel(Context context, DisplayRawContact rawContact,
+ long dataId, ContentValues contentValues, int labelResId, String fieldColumn,
+ String typeColumn, String labelColumn) {
+ super(context, rawContact);
+ mDataId = dataId;
+ mDataUri = ContentUris.withAppendedId(Data.CONTENT_URI, mDataId);
+ mContentValues = contentValues;
+ mLabelResId = labelResId;
+
+ mFieldColumn = fieldColumn;
+ mTypeColumn = typeColumn;
+ mLabelColumn = labelColumn;
+ }
+
+ @Override
+ public int getEntryType() {
+ return ViewTypes.FIELD_AND_TYPE;
+ }
+
+ @Override
+ public FieldAndTypeView getView(LayoutInflater inflater, View convertView, ViewGroup parent) {
+ final FieldAndTypeView result = convertView != null
+ ? (FieldAndTypeView) convertView
+ : FieldAndTypeView.inflate(inflater, parent, false);
+
+ result.setListener(mViewListener);
+ result.setLabelText(mLabelResId);
+ result.setFieldValue(getFieldValue());
+ result.setTypeDisplayLabel(getTypeDisplayLabel());
+
+ return result;
+ }
+
+ public long getDataId() {
+ return mDataId;
+ }
+
+ public ContentValues getContentValues() {
+ return mContentValues;
+ }
+
+ private void saveData() {
+ final ContentResolver resolver = getContext().getContentResolver();
+
+ final ArrayList<ContentProviderOperation> operations =
+ new ArrayList<ContentProviderOperation>();
+
+ final Builder builder;
+// if (getDataUri() == null) {
+// // INSERT
+// builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+// builder.withValue(Data.MIMETYPE, mMimeType);
+// builder.withValue(Data.RAW_CONTACT_ID, getRawContactId());
+// writeToBuilder(builder);
+// } else {
+ // UPDATE
+ builder = ContentProviderOperation.newUpdate(mDataUri);
+ writeToBuilder(builder);
+// }
+ operations.add(builder.build());
+
+ // Tell the Service to save
+ final Intent serviceIntent = new Intent();
+ final ContentProviderOperation[] operationsArray =
+ operations.toArray(ArrayUtils.emptyArray(ContentProviderOperation.class));
+ serviceIntent.putExtra(ContactSaveService.EXTRA_OPERATIONS, operationsArray);
+ serviceIntent.setClass(getContext().getApplicationContext(), ContactSaveService.class);
+
+ getContext().startService(serviceIntent);
+ }
+
+ private void writeToBuilder(final Builder builder) {
+ builder.withValue(mFieldColumn, getFieldValue());
+ builder.withValue(mTypeColumn, getType());
+ builder.withValue(mLabelColumn, getLabel());
+ }
+
+ protected String getFieldValue() {
+ return getContentValues().getAsString(mFieldColumn);
+ }
+
+ private void putFieldValue(String value) {
+ getContentValues().put(mFieldColumn, value);
+ }
+
+ public int getType() {
+ return getContentValues().getAsInteger(mTypeColumn).intValue();
+ }
+
+ private void putType(int value) {
+ getContentValues().put(mTypeColumn, value);
+ }
+
+ public String getLabel() {
+ return getContentValues().getAsString(mLabelColumn);
+ }
+
+ private void putLabel(String value) {
+ getContentValues().put(mLabelColumn, value);
+ }
+
+ protected abstract CharSequence getTypeDisplayLabel();
+
+ private FieldAndTypeView.Listener mViewListener = new FieldAndTypeView.Listener() {
+ public void onFocusLost(FieldAndTypeView view) {
+ Log.v(TAG, "Received FocusLost. Checking for changes");
+ boolean hasChanged = false;
+
+ final String oldValue = getFieldValue();
+ final String newValue = view.getFieldValue().toString();
+ if (!oldValue.equals(newValue)) {
+ putFieldValue(newValue);
+ hasChanged = true;
+ }
+ if (hasChanged) {
+ Log.v(TAG, "Found changes. Updating DB");
+ saveData();
+ }
+ }
+ };
+}
diff --git a/src/com/android/contacts/views/editor/viewModel/FooterViewModel.java b/src/com/android/contacts/views/editor/viewModel/FooterViewModel.java
new file mode 100644
index 0000000..9277a8b
--- /dev/null
+++ b/src/com/android/contacts/views/editor/viewModel/FooterViewModel.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.views.editor.viewModel;
+
+import com.android.contacts.views.editor.DisplayRawContact;
+import com.android.contacts.views.editor.view.FooterView;
+import com.android.contacts.views.editor.view.ViewTypes;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class FooterViewModel extends BaseViewModel {
+ private final Listener mListener;
+
+ public FooterViewModel(Context context, DisplayRawContact rawContact, Listener listener) {
+ super(context, rawContact);
+ if (listener == null) throw new IllegalArgumentException("listener must not be null");
+ mListener = listener;
+ }
+
+ @Override
+ public int getEntryType() {
+ return ViewTypes.RAW_CONTACT_FOOTER;
+ }
+
+ @Override
+ public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) {
+ final FooterView result = convertView != null
+ ? (FooterView) convertView
+ : FooterView.inflate(inflater, parent, false);
+
+ result.setListener(mViewListener);
+ return result;
+ }
+
+ private FooterView.Listener mViewListener = new FooterView.Listener() {
+ public void onAddClicked() {
+ if (mListener != null) mListener.onAddClicked(getRawContact());
+ }
+
+ public void onSeparateClicked() {
+ if (mListener != null) mListener.onAddClicked(getRawContact());
+ }
+
+ public void onDeleteClicked() {
+ if (mListener != null) mListener.onAddClicked(getRawContact());
+ }
+ };
+
+ public interface Listener {
+ public void onAddClicked(DisplayRawContact rawContact);
+ public void onSeparateClicked(DisplayRawContact rawContact);
+ public void onDeleteClicked(DisplayRawContact rawContact);
+ }
+}
diff --git a/src/com/android/contacts/views/editor/viewModel/HeaderViewModel.java b/src/com/android/contacts/views/editor/viewModel/HeaderViewModel.java
new file mode 100644
index 0000000..dca3a06
--- /dev/null
+++ b/src/com/android/contacts/views/editor/viewModel/HeaderViewModel.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.views.editor.viewModel;
+
+import com.android.contacts.R;
+import com.android.contacts.views.editor.DisplayRawContact;
+import com.android.contacts.views.editor.view.HeaderView;
+import com.android.contacts.views.editor.view.ViewTypes;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class HeaderViewModel extends BaseViewModel {
+ private boolean mCollapsed;
+
+ public HeaderViewModel(Context context, DisplayRawContact rawContact) {
+ super(context, rawContact);
+ }
+
+ public boolean isCollapsed() {
+ return mCollapsed;
+ }
+
+ public void setCollapsed(boolean collapsed) {
+ mCollapsed = collapsed;
+ }
+
+ @Override
+ public int getEntryType() {
+ return ViewTypes.RAW_CONTACT_HEADER;
+ }
+
+ @Override
+ public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) {
+ final HeaderView result = convertView != null
+ ? (HeaderView) convertView
+ : HeaderView.inflate(inflater, parent, false);
+
+ CharSequence accountType = getRawContact().getSource().getDisplayLabel(getContext());
+ if (TextUtils.isEmpty(accountType)) {
+ accountType = getContext().getString(R.string.account_phone);
+ }
+ final String accountName = getRawContact().getAccountName();
+
+ final String accountTypeDisplay;
+ if (TextUtils.isEmpty(accountName)) {
+ accountTypeDisplay = getContext().getString(R.string.account_type_format,
+ accountType);
+ } else {
+ accountTypeDisplay = getContext().getString(R.string.account_type_and_name,
+ accountType, accountName);
+ }
+
+ result.setCaptionText(accountTypeDisplay);
+ result.setLogo(getRawContact().getSource().getDisplayIcon(getContext()));
+
+ return result;
+ }
+}
diff --git a/src/com/android/contacts/views/editor/viewModel/PhoneViewModel.java b/src/com/android/contacts/views/editor/viewModel/PhoneViewModel.java
new file mode 100644
index 0000000..496bf2a
--- /dev/null
+++ b/src/com/android/contacts/views/editor/viewModel/PhoneViewModel.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.views.editor.viewModel;
+
+import com.android.contacts.views.editor.DisplayRawContact;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+
+public class PhoneViewModel extends FieldAndTypeViewModel {
+ private PhoneViewModel(Context context, DisplayRawContact rawContact, long dataId,
+ ContentValues contentValues, int titleResId) {
+ super(context, rawContact, dataId, contentValues, titleResId, Phone.NUMBER, Phone.TYPE,
+ Phone.LABEL);
+ }
+
+ public static PhoneViewModel createForExisting(Context context, DisplayRawContact rawContact,
+ long dataId, ContentValues contentValues, int titleResId) {
+ return new PhoneViewModel(context, rawContact, dataId, contentValues, titleResId);
+ }
+
+ @Override
+ protected CharSequence getTypeDisplayLabel() {
+ return Phone.getTypeLabel(getContext().getResources(), getType(), getLabel());
+ }
+}
diff --git a/src/com/android/contacts/widget/CompositeCursorAdapter.java b/src/com/android/contacts/widget/CompositeCursorAdapter.java
index c6d2ea3..147ed42 100644
--- a/src/com/android/contacts/widget/CompositeCursorAdapter.java
+++ b/src/com/android/contacts/widget/CompositeCursorAdapter.java
@@ -29,18 +29,29 @@
private static final int INITIAL_CAPACITY = 2;
- private static class Partition {
- final boolean showIfEmpty;
- final boolean hasHeader;
+ public static class Partition {
+ boolean showIfEmpty;
+ boolean hasHeader;
- int count;
Cursor cursor;
int idColumnIndex;
+ int count;
public Partition(boolean showIfEmpty, boolean hasHeader) {
this.showIfEmpty = showIfEmpty;
this.hasHeader = hasHeader;
}
+
+ /**
+ * True if the directory should be shown even if no contacts are found.
+ */
+ public boolean getShowIfEmpty() {
+ return showIfEmpty;
+ }
+
+ public boolean getHasHeader() {
+ return hasHeader;
+ }
}
private final Context mContext;
@@ -68,29 +79,65 @@
* list.
*/
public void addPartition(boolean showIfEmpty, boolean hasHeader) {
+ addPartition(new Partition(showIfEmpty, hasHeader));
+ }
+
+ public void addPartition(Partition partition) {
if (mSize >= mPartitions.length) {
int newCapacity = mSize + 2;
Partition[] newAdapters = new Partition[newCapacity];
System.arraycopy(mPartitions, 0, newAdapters, 0, mSize);
mPartitions = newAdapters;
}
- mPartitions[mSize++] = new Partition(showIfEmpty, hasHeader);
+ mPartitions[mSize++] = partition;
invalidate();
notifyDataSetChanged();
}
- public void resetPartitions() {
+ public void removePartition(int partitionIndex) {
+ Cursor cursor = mPartitions[partitionIndex].cursor;
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+
+ System.arraycopy(mPartitions, partitionIndex + 1, mPartitions, partitionIndex, mSize - 1);
+ mSize--;
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Removes cursors for all partitions, closing them as necessary.
+ */
+ public void clearPartitions() {
for (int i = 0; i < mSize; i++) {
Cursor cursor = mPartitions[i].cursor;
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
+ mPartitions[i].cursor = null;
}
- mSize = 0;
invalidate();
notifyDataSetChanged();
}
+ public void setHasHeader(int partitionIndex, boolean flag) {
+ mPartitions[partitionIndex].hasHeader = flag;
+ invalidate();
+ }
+
+ public void setShowIfEmpty(int partitionIndex, boolean flag) {
+ mPartitions[partitionIndex].showIfEmpty = flag;
+ invalidate();
+ }
+
+ public Partition getPartition(int partitionIndex) {
+ if (partitionIndex >= mSize) {
+ throw new ArrayIndexOutOfBoundsException(partitionIndex);
+ }
+ return mPartitions[partitionIndex];
+ }
+
protected void invalidate() {
mCacheValid = false;
}