Merge QP1A.190212.003
Change-Id: Id53bfa21dae871b31f2606f8f9fc1531f326d72f
diff --git a/Android.mk b/Android.mk
index 2a3a485..9e914c3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -29,6 +29,8 @@
guava \
libphonenumber
+LOCAL_JAVA_LIBRARIES := telephony-common voip-common
+
LOCAL_USE_AAPT2 := true
LOCAL_AAPT_FLAGS := \
@@ -43,7 +45,8 @@
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-LOCAL_SDK_VERSION := current
+LOCAL_PRIVATE_PLATFORM_APIS := true
+#LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 21
include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b0783d4..1ad051b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -515,6 +515,34 @@
android:theme="@style/PeopleThemeAppCompat.FullScreenDialog.SimImportActivity"/>
+ <activity
+ android:name=".activities.MultiPickContactsActivity"
+ android:windowSoftInputMode="stateHidden|adjustResize"
+ android:screenOrientation="nosensor"
+ android:theme="@style/PeopleTheme"
+ android:configChanges="orientation|keyboardHidden|layoutDirection|fontScale|locale">
+ <intent-filter>
+ <action android:name="com.android.contacts.action.MULTI_PICK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.dir/person"/>
+ <data android:mimeType="vnd.android.cursor.dir/contact"/>
+ <data android:mimeType="vnd.android.cursor.dir/raw_contact"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.android.contacts.action.MULTI_PICK_CONTACT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.dir/person"/>
+ <data android:mimeType="vnd.android.cursor.dir/contact"/>
+ <data android:mimeType="vnd.android.cursor.dir/raw_contact"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.android.contacts.action.MULTI_PICK_EMAIL"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:mimeType="vnd.android.cursor.dir/person"/>
+ <data android:mimeType="vnd.android.cursor.dir/contact"/>
+ <data android:mimeType="vnd.android.cursor.dir/raw_contact"/>
+ </intent-filter>
+ </activity>
<service
android:name=".vcard.VCardService"
android:exported="false"/>
diff --git a/res/drawable-hdpi/ic_contact_picture_sim.png b/res/drawable-hdpi/ic_contact_picture_sim.png
new file mode 100644
index 0000000..7b6cc26
--- /dev/null
+++ b/res/drawable-hdpi/ic_contact_picture_sim.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_contact_picture_sim_1.png b/res/drawable-hdpi/ic_contact_picture_sim_1.png
new file mode 100644
index 0000000..ec2ebcb
--- /dev/null
+++ b/res/drawable-hdpi/ic_contact_picture_sim_1.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_contact_picture_sim_2.png b/res/drawable-hdpi/ic_contact_picture_sim_2.png
new file mode 100644
index 0000000..3b68c68
--- /dev/null
+++ b/res/drawable-hdpi/ic_contact_picture_sim_2.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_contact_picture_sim.png b/res/drawable-mdpi/ic_contact_picture_sim.png
new file mode 100644
index 0000000..dd63d11
--- /dev/null
+++ b/res/drawable-mdpi/ic_contact_picture_sim.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_contact_picture_sim_1.png b/res/drawable-mdpi/ic_contact_picture_sim_1.png
new file mode 100644
index 0000000..4793a07
--- /dev/null
+++ b/res/drawable-mdpi/ic_contact_picture_sim_1.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_contact_picture_sim_2.png b/res/drawable-mdpi/ic_contact_picture_sim_2.png
new file mode 100644
index 0000000..665a671
--- /dev/null
+++ b/res/drawable-mdpi/ic_contact_picture_sim_2.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_contact_picture_sim.png b/res/drawable-xhdpi/ic_contact_picture_sim.png
new file mode 100644
index 0000000..a39b7df
--- /dev/null
+++ b/res/drawable-xhdpi/ic_contact_picture_sim.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_contact_picture_sim_1.png b/res/drawable-xhdpi/ic_contact_picture_sim_1.png
new file mode 100644
index 0000000..300c028
--- /dev/null
+++ b/res/drawable-xhdpi/ic_contact_picture_sim_1.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_contact_picture_sim_2.png b/res/drawable-xhdpi/ic_contact_picture_sim_2.png
new file mode 100644
index 0000000..c7ce422
--- /dev/null
+++ b/res/drawable-xhdpi/ic_contact_picture_sim_2.png
Binary files differ
diff --git a/res/drawable/expand_more.xml b/res/drawable/expand_more.xml
new file mode 100644
index 0000000..2d8b748
--- /dev/null
+++ b/res/drawable/expand_more.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24"
+ android:width="24dp" >
+
+ <path
+ android:fillColor="#ffffff"
+ android:pathData="M17,9.5l-5,5l-5-5H17z" />
+
+</vector>
diff --git a/res/drawable/quantum_ic_arrow_back_vd_theme_24.xml b/res/drawable/quantum_ic_arrow_back_vd_theme_24.xml
index 174cf12..bb87260 100644
--- a/res/drawable/quantum_ic_arrow_back_vd_theme_24.xml
+++ b/res/drawable/quantum_ic_arrow_back_vd_theme_24.xml
@@ -21,6 +21,6 @@
android:viewportHeight="24.0"
android:tint="?attr/colorControlNormal">
<path
- android:fillColor="@android:color/white"
+ android:fillColor="@android:color/darker_gray"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>
diff --git a/res/layout/action_mode.xml b/res/layout/action_mode.xml
new file mode 100644
index 0000000..6505a9b
--- /dev/null
+++ b/res/layout/action_mode.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/navigation_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal" >
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent" >
+ <ImageView android:layout_gravity="end|center"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:src="@drawable/expand_more"
+ android:tint="@android:color/white" />
+ <Button android:id="@+id/selection_menu"
+ style="?android:attr/actionButtonStyle"
+ android:layout_width="@dimen/action_bar_select_button_width"
+ android:layout_height="match_parent"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:divider="?android:attr/listDividerAlertDialog"
+ android:gravity="center_vertical|left"
+ android:paddingEnd="@dimen/action_bar_select_button_padding_end"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceLargePopupMenu"
+ android:textColor="@color/action_bar_select_button_text_color"/>
+ </FrameLayout>
+</LinearLayout>
diff --git a/res/layout/custom_pick_action_bar.xml b/res/layout/custom_pick_action_bar.xml
new file mode 100644
index 0000000..37603d9
--- /dev/null
+++ b/res/layout/custom_pick_action_bar.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="0dip"
+ android:layout_height="0dip" >
+
+ <View
+ android:layout_width="1px"
+ android:layout_height="1px"
+ android:focusable="true"
+ android:focusableInTouchMode="true" >
+
+ <requestFocus />
+ </View>
+
+ <EditText
+ android:id="@+id/search_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/search_box_icon_size"
+ android:layout_marginLeft="@dimen/search_box_text_left_margin"
+ android:background="@null"
+ android:fontFamily="@string/search_font_family"
+ android:imeOptions="flagNoExtractUi"
+ android:inputType="textFilter"
+ android:singleLine="true"
+ android:textColor="@color/multi_pick_search_view_edit_text_color"
+ android:textColorHint="@color/multi_pick_search_view_edit_hint_text_color"
+ android:textCursorDrawable="@null"
+ android:textSize="@dimen/contacts_text_size" />
+
+</FrameLayout>
diff --git a/res/layout/multi_pick_activity.xml b/res/layout/multi_pick_activity.xml
new file mode 100644
index 0000000..fee1624
--- /dev/null
+++ b/res/layout/multi_pick_activity.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/multi_pick_frame"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/multi_pick_top_divider"
+ android:layout_width="match_parent"
+ android:layout_height="2dp"
+ android:background="@color/divider_line_color"
+ android:elevation="@dimen/multi_pick_top_divider_elevation"
+ android:visibility="gone" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" >
+ <FrameLayout
+ android:id="@+id/pick_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@color/divider_line_color" />
+
+ <TextView
+ android:id="@+id/btn_ok"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/multi_pick_linear_layout_for_button_height"
+ android:layout_gravity="bottom"
+ android:enabled="false"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:fontFamily="sans-serif-medium"
+ android:gravity="center"
+ android:text="@string/btn_ok"
+ android:textSize="@dimen/contacts_text_size" />
+
+</LinearLayout>
diff --git a/res/layout/multi_pick_contact_item.xml b/res/layout/multi_pick_contact_item.xml
new file mode 100644
index 0000000..be78f7a
--- /dev/null
+++ b/res/layout/multi_pick_contact_item.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/multi_pick_contacts_item_height"
+ android:descendantFocusability="blocksDescendants"
+ android:minHeight="?android:attr/listPreferredItemHeight" >
+
+ <TextView
+ android:id="@+id/section_index"
+ android:layout_width="@dimen/contact_section_index_width"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"
+ android:layout_marginStart="16dp"
+ android:textColor="@color/contact_section_index_color"
+ android:textSize="@dimen/contact_section_text_size"
+ android:visibility="gone" />
+
+ <ImageView
+ android:id="@+id/pick_contact_photo"
+ android:layout_width="@dimen/pick_contact_photo_size"
+ android:layout_height="@dimen/pick_contact_photo_size"
+ android:layout_centerVertical="true"
+ android:layout_marginStart="@dimen/multi_pick_linear_layout_padding"
+ android:layout_toRightOf="@+id/section_index"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_centerVertical="true"
+ android:layout_marginStart="@dimen/multi_pick_linear_layout_padding"
+ android:layout_toEndOf="@+id/pick_contact_photo"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" >
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:paddingEnd="@dimen/multi_pick_linear_layout_padding" >
+
+ <TextView
+ android:id="@+id/pick_contact_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignTop="@+id/section_index"
+ android:layout_toEndOf="@+id/pick_contact_photo"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textColor="@color/contacts_or_name_text_color"
+ android:textSize="@dimen/contacts_text_size" />
+
+ <TextView
+ android:id="@+id/pick_contact_number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/multi_pick_label_text_color"
+ android:textSize="@dimen/phone_or_number_text_size" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/label"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/multi_pick_linear_layout_padding"
+ android:gravity="center"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/multi_pick_label_text_color"
+ android:textSize="@dimen/multi_pick_label_text_size" />
+ <CheckBox
+ android:id="@+id/pick_contact_check"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="38dip"
+ android:layout_centerVertical="true"
+ android:layout_alignParentRight="true"
+ android:clickable = "false">
+ </CheckBox>
+ </LinearLayout>
+
+</RelativeLayout>
diff --git a/res/layout/multi_pick_contacts_fragment.xml b/res/layout/multi_pick_contacts_fragment.xml
new file mode 100644
index 0000000..96ff727
--- /dev/null
+++ b/res/layout/multi_pick_contacts_fragment.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#fafafa"
+ android:orientation="vertical">
+
+ <ListView
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1.0"
+ android:divider="@null"
+ android:fastScrollEnabled="true" />
+
+</LinearLayout>
diff --git a/res/layout/popup_list_item.xml b/res/layout/popup_list_item.xml
new file mode 100644
index 0000000..517f42b
--- /dev/null
+++ b/res/layout/popup_list_item.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2014, 2016, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/popup_list_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLargePopupMenu"
+ android:singleLine="true"
+ android:gravity="center_vertical"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:minHeight="48dp"
+ android:minWidth="124dp"
+ android:textColor="@android:color/black"
+/>
diff --git a/res/menu/quickcontact.xml b/res/menu/quickcontact.xml
index 7d87708..807c1a4 100644
--- a/res/menu/quickcontact.xml
+++ b/res/menu/quickcontact.xml
@@ -43,6 +43,18 @@
android:alphabeticShortcut="s" />
<item
+ android:id="@+id/menu_copy_to_phone"
+ android:title="@string/menu_copyTo" />
+
+ <item
+ android:id="@+id/menu_copy_to_sim1"
+ android:title="@string/menu_copyTo" />
+
+ <item
+ android:id="@+id/menu_copy_to_sim2"
+ android:title="@string/menu_copyTo" />
+
+ <item
android:id="@+id/menu_create_contact_shortcut"
android:title="@string/menu_create_contact_shortcut" />
diff --git a/res/menu/search_menu.xml b/res/menu/search_menu.xml
index bf4b5dd..56a55d9 100644
--- a/res/menu/search_menu.xml
+++ b/res/menu/search_menu.xml
@@ -19,5 +19,6 @@
android:id="@+id/menu_search"
android:icon="@drawable/quantum_ic_search_vd_theme_24"
android:title="@string/hint_findContacts"
+ android:showAsAction="always"
contacts:showAsAction="always" />
</menu>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index e5007f0..6b96704 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -511,4 +511,36 @@
<string name="contacts_default_notification_channel" msgid="4754058700611188581">"通知"</string>
<string name="yes_button" msgid="1268479086848288060">"是"</string>
<string name="no_button" msgid="5742815694687835125">"否"</string>
+ <!-- no translation found for contacts_default_notification_channel (4754058700611188581) -->
+ <skip />
+ <string name="select_all">"全选"</string>
+ <string name="btn_ok">"确定"</string>
+ <string name="btn_cancel">"取消"</string>
+ <string name="deleteConfirmation_title">"要删除联系人吗?"</string>
+ <string name="ContactMultiDeleteConfirmation">"将会删除这些联系人。"</string>
+ <string name="delete_contacts_title">"删除联系人"</string>
+ <string name="delete_contacts_message">"正在删除..."</string>
+ <string name="delete_termination">"删除结束"</string>
+ <string name="too_many_contacts_add_to_group">联系人过多,应该少于<xliff:g id="count">%d</xliff:g>个</string>
+ <string name="contacts_selected">已选中%d个</string>
+
+ <string name="summary_count_numbers">%s 个人</string>
+ <!-- The hint text in search-view.-->
+ <string name="search_menu_search">搜索</string>
+ <string name="too_many_contacts_add">联系人太多,应该少于 <xliff:g id="count">%d</xliff:g>个</string>
+ <string name="menu_select_all" msgid="621719255150713545">"全选"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"取消全选"</string>
+ <string name="export_to_sim">"导出到 SIM 卡"</string>
+ <string name="exporting">"正在导出"</string>
+ <string name="progressdialog_cancel">取消</string>
+ <string name="sim_contact_export_failed">导出失败, 名字: <xliff:g id="name">%1$s</xliff:g>, 号码: <xliff:g id="number">%2$s</xliff:g>, 邮箱: <xliff:g id="email">%3$s</xliff:g></string>
+ <string name="export_sim_card_full">"SIM卡已满,<xliff:g id="insertCount">%d</xliff:g>条成功导出"</string>
+ <string name="export_finished">"导出结束"</string>
+ <string name="export_cancelled">导出操作被取消,<xliff:g id="insertCount">%s</xliff:g>条成功导出</string>
+ <string name="export_no_phone_or_email">导出失败, <xliff:g id="name">%s</xliff:g>没有电话号码或者邮箱地址</string>
+ <string name="copy_done">复制成功!</string>
+ <string name="copy_failure">复制失败!</string>
+ <string name="card_no_space">卡记录已满,部分信息未复制</string>
+ <string name="menu_copyTo">"复制到"</string>
+ <string name="no_empty_email_in_usim">"卡上电子邮件空间已满,部分邮件信息将不会复制 "</string>
</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index aeba877..d6d15a1 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -255,5 +255,29 @@
<!-- Highlight color used in places such as ripples -->
<color name="control_highlight_color">#1A000000</color>
+ <!-- The text color of contacts item's label. -->
+ <color name="multi_pick_label_text_color">#91000000</color>
+ <!-- Contacts item's text-colors. -->
+ <color name="contacts_or_name_text_color">#DF000000</color>
+ <!-- Confirm or disable button's text-colors in multi-pick. -->
+ <color name="ok_or_clear_button_disable_color">#44000000</color>
+ <color name="ok_or_clear_button_normal_color">#ff03a9f4</color>
+ <!-- Horizontal separator line should be 10% dark in the light theme. -->
+ <color name="divider_line_color">#1A000000</color>
+
+ <!-- Search-box's colors. -->
+ <color name="searchbox_phone_text_color">#db000000</color>
+ <color name="searchbox_phone_background_color">#ffffffff</color>
+ <color name="searchbox_phone_hint_text_color">#42000000</color>
+
+ <!-- The selection button's expand window text-color in actionbar. -->
+ <color name="action_bar_expand_text_color">#FF000000</color>
+ <color name="action_bar_select_button_text_color">#FFFFFFFF</color>
+ <color name="multi_pick_search_view_edit_text_color">#DBFFFFFF</color>
+ <color name="multi_pick_search_view_edit_hint_text_color">#42FFFFFF</color>
+ <!-- Contact section index's text-color. -->
+ <color name="contact_section_index_color">#8C000000</color>
+ <!-- Popup select button background. -->
+ <color name="select_popup_button_background">#FFFFFFFF</color>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 6d100ac..f5dee49 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -382,4 +382,51 @@
<!-- Side padding within the navigation drawer -->
<dimen name="drawer_label_header_end_padding">24dp</dimen>
+ <!-- Multi-pick contacts' list item height. -->
+ <dimen name="multi_pick_contacts_item_height">72dp</dimen>
+
+ <!-- The linear-layout's padding-end of Multi-pick list item. -->
+ <dimen name="multi_pick_linear_layout_padding">16dp</dimen>
+
+ <dimen name="multi_pick_linear_layout_for_button_height">54dp</dimen>
+ <!-- Label's text-size in contacts item. e.g.Home, Mobile, Custom. -->
+ <dimen name="multi_pick_label_text_size">14sp</dimen>
+
+ <!-- Phone number text-size in one contacts item. -->
+ <dimen name="phone_or_number_text_size">14sp</dimen>
+ <!-- Contacts name text-size in one contacts item. -->
+ <dimen name="contacts_text_size">16sp</dimen>
+
+ <!-- The index header view's width in multi-pick. e.g.A, B, C, #.-->
+ <dimen name="contact_section_index_width">40dp</dimen>
+ <!-- The index header text-size. -->
+ <dimen name="contact_section_text_size">24sp</dimen>
+ <!-- The index header's margin-start. -->
+ <dimen name="contact_section_index_margin_start">16dp</dimen>
+ <!-- The index header's margin-end. -->
+ <dimen name="contact_section_index_margin_end">42dp</dimen>
+ <dimen name="pick_contact_photo_size">40dp</dimen>
+
+ <!-- "OK" or "CLEAR" button's margin-end. -->
+ <dimen name="ok_or_clear_button_margin_end">14dp</dimen>
+
+ <!-- Navigation view's margin-start in actionbar. -->
+ <dimen name="navigation_image_view_margin_start">16dp</dimen>
+ <!-- Navigation view's margin-end in actionbar. -->
+ <dimen name="navigation_image_view_margin_end">20dp</dimen>
+
+ <!-- Selection button's width in actionbar. -->
+ <dimen name="action_bar_select_button_width">80dp</dimen>
+ <!-- Selection button's padding-end in actionbar. -->
+ <dimen name="action_bar_select_button_padding_end">25dp</dimen>
+
+ <!-- Popup window's width after click selection button. -->
+ <dimen name="action_bar_select_view_width">124dp</dimen>
+ <!-- Selected the same contact item's height. -->
+ <dimen name="pick_contact_same_item_height">54dp</dimen>
+ <!-- Select the first contact height in some same contacts. -->
+ <dimen name="pick_contact_first_item_height">72dp</dimen>
+ <!-- The elevation multi-pick activity's top divider. -->
+ <dimen name="multi_pick_top_divider_elevation">10dp</dimen>
+ <dimen name="header_listview_height">12dp</dimen>
</resources>
diff --git a/res/values/ids.xml b/res/values/ids.xml
index a4b8b4b..42760f6 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -20,6 +20,7 @@
<item type="id" name="dialog_sync_add"/>
<!-- For ContactDeletionInteraction -->
+ <item type="id" name="dialog_delete_contact_confirmation"/>
<item type="id" name="dialog_delete_contact_loader_id" />
<!-- For ContactMultiDeletionInteraction -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 56c4aaa..586c66e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1533,4 +1533,36 @@
<!-- Text of Negative Button in dialog -->
<string name="no_button">No</string>
-</resources>
\ No newline at end of file
+ <string name="select_all">All</string>
+ <string name="btn_ok">Ok</string>
+ <string name="btn_cancel">Cancel</string>
+ <string name="deleteConfirmation_title">Delete contact?</string>
+ <string name="ContactMultiDeleteConfirmation">These contacts will be deleted.</string>
+ <string name="delete_termination">"Finished deleting"</string>
+ <string name="delete_contacts_title">Delete Contacts</string>
+ <string name="delete_contacts_message">Deleting Contacts ...</string>
+ <string name="contacts_selected">%d selected</string>
+ <string name="summary_count_numbers">%s numbers</string>
+ <string name="search_menu_search">Search</string>
+ <string name="too_many_contacts_add">Too many contacts, that should be less than <xliff:g id="count">%d</xliff:g></string>
+ <!-- Label to select all contacts in multiple picker -->
+ <string name="menu_select_all">"Select all"</string>
+
+ <!-- Label to clear all selection in multiple picker -->
+ <string name="menu_select_none">"Unselect all"</string>
+ <!-- Action that exports all contacts to SIM -->
+ <string name="export_to_sim">Export to SIM card</string>
+ <string name="progressdialog_cancel">Cancel</string>
+ <string name="sim_contact_export_failed">Export failed, Contact name: <xliff:g id="name">%1$s</xliff:g>, number: <xliff:g id="number">%2$s</xliff:g>, email: <xliff:g id="email">%3$s</xliff:g></string>
+ <string name="export_sim_card_full">Sim Card is full, <xliff:g id="insertCount">%d</xliff:g> items are exported</string>
+ <string name="sim_card_full">Error, Sim Card is full.</string>
+ <string name="export_finished">Export finished</string>
+ <string name="export_cancelled">Export is canceled, <xliff:g id="insertCount">%s</xliff:g>items are exported</string>
+ <string name="export_no_phone_or_email">Export failed, <xliff:g id="name">%s</xliff:g>has not phone number or email address</string>
+ <string name="exporting">Exporting</string>
+ <string name="copy_done">Copy done!</string>
+ <string name="copy_failure">Copy Failed!</string>
+ <string name="card_no_space">Card has no space ,some record copy failed</string>
+ <string name="menu_copyTo">"Copy to "</string>
+ <string name="no_empty_email_in_usim">"Email is full, some record copy failed "</string>
+</resources>
diff --git a/src/com/android/contacts/ContactPhotoManager.java b/src/com/android/contacts/ContactPhotoManager.java
index e5f808d..8b40613 100644
--- a/src/com/android/contacts/ContactPhotoManager.java
+++ b/src/com/android/contacts/ContactPhotoManager.java
@@ -16,6 +16,7 @@
package com.android.contacts;
+import android.accounts.Account;
import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
import android.content.ContentResolver;
@@ -123,17 +124,23 @@
*/
public static Drawable getDefaultAvatarDrawableForContact(Resources resources, boolean hires,
DefaultImageRequest defaultImageRequest) {
+ return getDefaultAvatarDrawableForContact(resources, hires,
+ defaultImageRequest, null);
+ }
+
+ public static Drawable getDefaultAvatarDrawableForContact(Resources resources,
+ boolean hires, DefaultImageRequest defaultImageRequest, Account account) {
if (defaultImageRequest == null) {
if (sDefaultLetterAvatar == null) {
// Cache and return the letter tile drawable that is created by a null request,
// so that it doesn't have to be recreated every time it is requested again.
sDefaultLetterAvatar = LetterTileDefaultImageProvider.getDefaultImageForContact(
- resources, null);
+ resources, null, account);
}
return sDefaultLetterAvatar;
}
return LetterTileDefaultImageProvider.getDefaultImageForContact(resources,
- defaultImageRequest);
+ defaultImageRequest, account);
}
/**
@@ -375,6 +382,10 @@
*/
public abstract void applyDefaultImage(ImageView view, int extent, boolean darkTheme,
DefaultImageRequest defaultImageRequest);
+
+ public void applyDefaultImage(ImageView view, Account account, int extent,
+ boolean darkTheme, DefaultImageRequest defaultImageRequest) {
+ }
}
/**
@@ -386,14 +397,20 @@
@Override
public void applyDefaultImage(ImageView view, int extent, boolean darkTheme,
DefaultImageRequest defaultImageRequest) {
+ applyDefaultImage(view, null, extent, darkTheme, defaultImageRequest);
+ }
+
+ @Override
+ public void applyDefaultImage(ImageView view, Account account, int extent,
+ boolean darkTheme, DefaultImageRequest defaultImageRequest) {
final Drawable drawable = getDefaultImageForContact(view.getResources(),
- defaultImageRequest);
+ defaultImageRequest, account);
view.setImageDrawable(drawable);
}
public static Drawable getDefaultImageForContact(Resources resources,
- DefaultImageRequest defaultImageRequest) {
- final LetterTileDrawable drawable = new LetterTileDrawable(resources);
+ DefaultImageRequest defaultImageRequest, Account account) {
+ final LetterTileDrawable drawable = new LetterTileDrawable(resources, account);
if (defaultImageRequest != null) {
// If the contact identifier is null or empty, fallback to the
// displayName. In that case, use {@code null} for the contact's
@@ -460,8 +477,8 @@
* it is displayed immediately. Otherwise a request is sent to load the photo
* from the database.
*/
- public abstract void loadThumbnail(ImageView view, long photoId, boolean darkTheme,
- boolean isCircular, DefaultImageRequest defaultImageRequest,
+ public abstract void loadThumbnail(ImageView view, long photoId, Account account,
+ boolean darkTheme, boolean isCircular, DefaultImageRequest defaultImageRequest,
DefaultImageProvider defaultProvider);
/**
@@ -470,9 +487,22 @@
*/
public final void loadThumbnail(ImageView view, long photoId, boolean darkTheme,
boolean isCircular, DefaultImageRequest defaultImageRequest) {
- loadThumbnail(view, photoId, darkTheme, isCircular, defaultImageRequest, DEFAULT_AVATAR);
+ loadThumbnail(view, photoId, null, darkTheme, isCircular, defaultImageRequest,
+ DEFAULT_AVATAR);
}
+ public final void loadThumbnail(ImageView view, long photoId, Account account,
+ boolean darkTheme, boolean isCircular, DefaultImageRequest defaultImageRequest) {
+ loadThumbnail(view, photoId, account, darkTheme, isCircular,
+ defaultImageRequest, DEFAULT_AVATAR);
+ }
+
+ public final void loadThumbnail(ImageView view, long photoId, boolean darkTheme,
+ boolean isCircular, DefaultImageRequest defaultImageRequest,
+ DefaultImageProvider defaultProvider) {
+ loadThumbnail(view, photoId, null, darkTheme, isCircular,
+ defaultImageRequest, defaultProvider);
+ }
/**
* Load photo into the supplied image view. If the photo is already cached,
@@ -491,9 +521,9 @@
* @param defaultProvider The provider of default avatars (this is used if photoUri doesn't
* refer to an existing image)
*/
- public abstract void loadPhoto(ImageView view, Uri photoUri, int requestedExtent,
- boolean darkTheme, boolean isCircular, DefaultImageRequest defaultImageRequest,
- DefaultImageProvider defaultProvider);
+ public abstract void loadPhoto(ImageView view, Uri photoUri, Account account,
+ int requestedExtent, boolean darkTheme, boolean isCircular, DefaultImageRequest
+ defaultImageRequest, DefaultImageProvider defaultProvider);
/**
* Calls {@link #loadPhoto(ImageView, Uri, int, boolean, DefaultImageRequest,
@@ -505,10 +535,22 @@
*/
public final void loadPhoto(ImageView view, Uri photoUri, int requestedExtent,
boolean darkTheme, boolean isCircular, DefaultImageRequest defaultImageRequest) {
- loadPhoto(view, photoUri, requestedExtent, darkTheme, isCircular,
+ loadPhoto(view, photoUri, null, requestedExtent, darkTheme, isCircular,
defaultImageRequest, DEFAULT_AVATAR);
}
+ public final void loadPhoto(ImageView view, Uri photoUri, Account account, int requestedExtent,
+ boolean darkTheme, boolean isCircular, DefaultImageRequest defaultImageRequest) {
+ loadPhoto(view, photoUri, account, requestedExtent, darkTheme, isCircular,
+ defaultImageRequest, DEFAULT_AVATAR);
+ }
+
+ public final void loadPhoto(ImageView view, Uri photoUri, int requestedExtent,
+ boolean darkTheme, boolean isCircular, DefaultImageRequest defaultImageRequest,
+ DefaultImageProvider defaultProvider) {
+ loadPhoto(view, photoUri, null, requestedExtent, darkTheme, isCircular,
+ defaultImageRequest, defaultProvider);
+ }
/**
* Calls {@link #loadPhoto(ImageView, Uri, boolean, boolean, DefaultImageRequest,
* DefaultImageProvider)} with {@link #DEFAULT_AVATAR} and with the assumption, that
@@ -519,7 +561,15 @@
*/
public final void loadDirectoryPhoto(ImageView view, Uri photoUri, boolean darkTheme,
boolean isCircular, DefaultImageRequest defaultImageRequest) {
- loadPhoto(view, photoUri, -1, darkTheme, isCircular, defaultImageRequest, DEFAULT_AVATAR);
+ loadPhoto(view, photoUri, null, -1, darkTheme, isCircular, defaultImageRequest,
+ DEFAULT_AVATAR);
+ }
+
+ public final void loadDirectoryPhoto(ImageView view, Uri photoUri,
+ Account account, boolean darkTheme, boolean isCircular,
+ DefaultImageRequest defaultImageRequest) {
+ loadPhoto(view, photoUri, account, -1, darkTheme, isCircular,
+ defaultImageRequest, DEFAULT_AVATAR);
}
/**
@@ -821,11 +871,12 @@
}
@Override
- public void loadThumbnail(ImageView view, long photoId, boolean darkTheme, boolean isCircular,
- DefaultImageRequest defaultImageRequest, DefaultImageProvider defaultProvider) {
+ public void loadThumbnail(ImageView view, long photoId, Account account, boolean darkTheme,
+ boolean isCircular, DefaultImageRequest defaultImageRequest, DefaultImageProvider
+ defaultProvider) {
if (photoId == 0) {
// No photo is needed
- defaultProvider.applyDefaultImage(view, -1, darkTheme, defaultImageRequest);
+ defaultProvider.applyDefaultImage(view, account, -1, darkTheme, defaultImageRequest);
mPendingRequests.remove(view);
} else {
if (DEBUG) Log.d(TAG, "loadPhoto request: " + photoId);
@@ -835,19 +886,19 @@
}
@Override
- public void loadPhoto(ImageView view, Uri photoUri, int requestedExtent, boolean darkTheme,
- boolean isCircular, DefaultImageRequest defaultImageRequest,
+ public void loadPhoto(ImageView view, Uri photoUri, Account account, int requestedExtent,
+ boolean darkTheme, boolean isCircular, DefaultImageRequest defaultImageRequest,
DefaultImageProvider defaultProvider) {
if (photoUri == null) {
// No photo is needed
- defaultProvider.applyDefaultImage(view, requestedExtent, darkTheme,
+ defaultProvider.applyDefaultImage(view, account, requestedExtent, darkTheme,
defaultImageRequest);
mPendingRequests.remove(view);
} else {
if (DEBUG) Log.d(TAG, "loadPhoto request: " + photoUri);
if (isDefaultImageUri(photoUri)) {
- createAndApplyDefaultImageForUri(view, photoUri, requestedExtent, darkTheme,
- isCircular, defaultProvider);
+ createAndApplyDefaultImageForUri(view, photoUri, account, requestedExtent,
+ darkTheme, isCircular, defaultProvider);
} else {
loadPhotoByIdOrUri(view, Request.createFromUri(photoUri, requestedExtent,
darkTheme, isCircular, defaultProvider, defaultImageRequest));
@@ -855,11 +906,12 @@
}
}
- private void createAndApplyDefaultImageForUri(ImageView view, Uri uri, int requestedExtent,
- boolean darkTheme, boolean isCircular, DefaultImageProvider defaultProvider) {
+ private void createAndApplyDefaultImageForUri(ImageView view, Uri uri, Account account,
+ int requestedExtent, boolean darkTheme, boolean isCircular, DefaultImageProvider
+ defaultProvider) {
DefaultImageRequest request = getDefaultImageRequestFromUri(uri);
request.isCircular = isCircular;
- defaultProvider.applyDefaultImage(view, requestedExtent, darkTheme, request);
+ defaultProvider.applyDefaultImage(view, account, requestedExtent, darkTheme, request);
}
private void loadPhotoByIdOrUri(ImageView view, Request request) {
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
old mode 100755
new mode 100644
index 9f25726..3e7a94d
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -50,9 +50,12 @@
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.support.v4.os.ResultReceiver;
import android.text.TextUtils;
+import android.telephony.SubscriptionManager;
import android.util.Log;
import android.widget.Toast;
+import com.android.contacts.ContactUtils;
+import com.android.contacts.SimContactsOperation;
import com.android.contacts.activities.ContactEditorActivity;
import com.android.contacts.compat.CompatUtils;
import com.android.contacts.compat.PinnedPositionsCompat;
@@ -189,6 +192,8 @@
private static final int PERSIST_TRIES = 3;
+ private static SimContactsOperation mSimContactsOperation;
+
private static final int MAX_CONTACTS_PROVIDER_BATCH_SIZE = 499;
public interface Listener {
@@ -216,6 +221,7 @@
super.onCreate();
mGroupsDao = new GroupsDaoImpl(this);
mSimContactDao = SimContactDao.create(this);
+ mSimContactsOperation = new SimContactsOperation(this);
}
public static void registerListener(Listener listener) {
@@ -511,128 +517,149 @@
long insertedRawContactId = -1;
// Attempt to persist changes
+ // flag indicate whether contact need saved to local database, only set false when
+ // sim contacts saved failure,otherwise it always true
+ boolean needSaveToLocal = true;
+
+ ArrayList<Long> rawContactsList = new ArrayList<Long>();
+ for (int i=0; i < state.size(); i++) {
+ final RawContactDelta entity = state.get(i);
+ final String accountType = entity.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+ final String accountName = entity.getValues().getAsString(RawContacts.ACCOUNT_NAME);
+ rawContactsList.add(entity.getRawContactId());
+ final int subscription = ContactUtils.getSubscription(
+ accountType, accountName);
+ boolean isCardOperation = (subscription != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ ? true : false;
+ if (isCardOperation) {
+ needSaveToLocal = doSaveToSimCard(entity, resolver, subscription);
+ Log.d(TAG, "doSaveToSimCard result is " + needSaveToLocal);
+ }
+ }
int tries = 0;
while (tries++ < PERSIST_TRIES) {
- try {
- // Build operations and try applying
- final ArrayList<CPOWrapper> diffWrapper = state.buildDiffWrapper();
+ if (needSaveToLocal) {
+ try {
+ // Build operations and try applying
+ final ArrayList<CPOWrapper> diffWrapper = state.buildDiffWrapper();
- final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
- for (CPOWrapper cpoWrapper : diffWrapper) {
- diff.add(cpoWrapper.getOperation());
- }
-
- if (DEBUG) {
- Log.v(TAG, "Content Provider Operations:");
- for (ContentProviderOperation operation : diff) {
- Log.v(TAG, operation.toString());
+ for (CPOWrapper cpoWrapper : diffWrapper) {
+ diff.add(cpoWrapper.getOperation());
}
- }
- int numberProcessed = 0;
- boolean batchFailed = false;
- final ContentProviderResult[] results = new ContentProviderResult[diff.size()];
- while (numberProcessed < diff.size()) {
- final int subsetCount = applyDiffSubset(diff, numberProcessed, results, resolver);
- if (subsetCount == -1) {
- Log.w(TAG, "Resolver.applyBatch failed in saveContacts");
- batchFailed = true;
- break;
- } else {
- numberProcessed += subsetCount;
+ if (DEBUG) {
+ Log.v(TAG, "Content Provider Operations:");
+ for (ContentProviderOperation operation : diff) {
+ Log.v(TAG, operation.toString());
+ }
}
- }
- if (batchFailed) {
- // Retry save
- continue;
- }
+ int numberProcessed = 0;
+ boolean batchFailed = false;
+ final ContentProviderResult[] results = new ContentProviderResult[diff.size()];
+ while (numberProcessed < diff.size()) {
+ final int subsetCount = applyDiffSubset(diff, numberProcessed, results, resolver);
+ if (subsetCount == -1) {
+ Log.w(TAG, "Resolver.applyBatch failed in saveContacts");
+ batchFailed = true;
+ break;
+ } else {
+ numberProcessed += subsetCount;
+ }
+ }
- final long rawContactId = getRawContactId(state, diffWrapper, results);
- if (rawContactId == -1) {
- throw new IllegalStateException("Could not determine RawContact ID after save");
- }
- // We don't have to check to see if the value is still -1. If we reach here,
- // the previous loop iteration didn't succeed, so any ID that we obtained is bogus.
- insertedRawContactId = getInsertedRawContactId(diffWrapper, results);
- if (isProfile) {
- // Since the profile supports local raw contacts, which may have been completely
- // removed if all information was removed, we need to do a special query to
- // get the lookup URI for the profile contact (if it still exists).
- Cursor c = resolver.query(Profile.CONTENT_URI,
- new String[] {Contacts._ID, Contacts.LOOKUP_KEY},
- null, null, null);
- if (c == null) {
+ if (batchFailed) {
+ // Retry save
continue;
}
- try {
- if (c.moveToFirst()) {
- final long contactId = c.getLong(0);
- final String lookupKey = c.getString(1);
- lookupUri = Contacts.getLookupUri(contactId, lookupKey);
- }
- } finally {
- c.close();
+
+ final long rawContactId = getRawContactId(state, diffWrapper, results);
+ if (rawContactId == -1) {
+ throw new IllegalStateException("Could not determine RawContact ID after save");
}
- } else {
- final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
- rawContactId);
- lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri);
- }
- if (lookupUri != null && Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Saved contact. New URI: " + lookupUri);
- }
-
- // We can change this back to false later, if we fail to save the contact photo.
- succeeded = true;
- break;
-
- } catch (RemoteException e) {
- // Something went wrong, bail without success
- FeedbackHelper.sendFeedback(this, TAG, "Problem persisting user edits", e);
- break;
-
- } catch (IllegalArgumentException e) {
- // This is thrown by applyBatch on malformed requests
- FeedbackHelper.sendFeedback(this, TAG, "Problem persisting user edits", e);
- showToast(R.string.contactSavedErrorToast);
- break;
-
- } catch (OperationApplicationException e) {
- // Version consistency failed, re-parent change and try again
- Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
- final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN(");
- boolean first = true;
- final int count = state.size();
- for (int i = 0; i < count; i++) {
- Long rawContactId = state.getRawContactId(i);
- if (rawContactId != null && rawContactId != -1) {
- if (!first) {
- sb.append(',');
+ // We don't have to check to see if the value is still -1. If we reach here,
+ // the previous loop iteration didn't succeed, so any ID that we obtained is bogus.
+ insertedRawContactId = getInsertedRawContactId(diffWrapper, results);
+ if (isProfile) {
+ // Since the profile supports local raw contacts, which may have been completely
+ // removed if all information was removed, we need to do a special query to
+ // get the lookup URI for the profile contact (if it still exists).
+ Cursor c = resolver.query(Profile.CONTENT_URI,
+ new String[] {Contacts._ID, Contacts.LOOKUP_KEY },
+ null, null, null);
+ if (c == null) {
+ continue;
}
- sb.append(rawContactId);
- first = false;
+ try {
+ if (c.moveToFirst()) {
+ final long contactId = c.getLong(0);
+ final String lookupKey = c.getString(1);
+ lookupUri = Contacts.getLookupUri(contactId, lookupKey);
+ }
+ } finally {
+ c.close();
+ }
+ } else {
+ final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
+ rawContactId);
+ lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri);
}
- }
- sb.append(")");
+ if (lookupUri != null && Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Saved contact. New URI: " + lookupUri);
+ }
- if (first) {
- throw new IllegalStateException(
- "Version consistency failed for a new contact", e);
- }
+ // We can change this back to false later, if we fail to save the contact photo.
+ succeeded = true;
+ break;
- final RawContactDeltaList newState = RawContactDeltaList.fromQuery(
- isProfile
- ? RawContactsEntity.PROFILE_CONTENT_URI
- : RawContactsEntity.CONTENT_URI,
- resolver, sb.toString(), null, null);
- state = RawContactDeltaList.mergeAfter(newState, state);
+ } catch (RemoteException e) {
+ // Something went wrong, bail without success
+ FeedbackHelper.sendFeedback(this, TAG, "Problem persisting user edits", e);
+ break;
- // Update the new state to use profile URIs if appropriate.
- if (isProfile) {
- for (RawContactDelta delta : state) {
- delta.setProfileQueryUri();
+ } catch (IllegalArgumentException e) {
+ // This is thrown by applyBatch on malformed requests
+ FeedbackHelper.sendFeedback(this, TAG, "Problem persisting user edits", e);
+ showToast(R.string.contactSavedErrorToast);
+ break;
+
+ } catch (OperationApplicationException e) {
+ // Version consistency failed, re-parent change and try again
+ Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
+ final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN(");
+ boolean first = true;
+ final int count = state.size();
+ for (int i = 0; i < count; i++) {
+ Long rawContactId = state.getRawContactId(i);
+ if (rawContactId != null && rawContactId != -1) {
+ if (!first) {
+ sb.append(',');
+ }
+ sb.append(rawContactId);
+ first = false;
+ }
+ }
+ sb.append(")");
+
+ if (first) {
+ throw new IllegalStateException(
+ "Version consistency failed for a new contact", e);
+ }
+
+ final RawContactDeltaList newState = RawContactDeltaList.fromQuery(
+ isProfile
+ ? RawContactsEntity.PROFILE_CONTENT_URI
+ : RawContactsEntity.CONTENT_URI,
+ resolver, sb.toString(), null, null);
+ state = RawContactDeltaList.mergeAfter(newState, state);
+
+ // Update the new state to use profile URIs if appropriate.
+ if (isProfile) {
+ for (RawContactDelta delta : state) {
+ delta.setProfileQueryUri();
+ }
}
}
}
@@ -671,6 +698,22 @@
}
}
+ private boolean doSaveToSimCard(RawContactDelta entity, ContentResolver resolver,
+ int slot) {
+ boolean isInsert = entity.isContactInsert();
+ ContentValues values = entity.buildSimDiff();
+ if (isInsert) {
+ Uri resultUri = mSimContactsOperation.insert(values, slot);
+ if (resultUri != null)
+ return true;
+ } else {
+ int resultInt = mSimContactsOperation.update(values, slot);
+ if (resultInt == 1)
+ return true;
+ }
+ return false;
+ }
+
/**
* Splits "diff" into subsets based on "MAX_CONTACTS_PROVIDER_BATCH_SIZE", applies each of the
* subsets, adds the returned array to "results".
@@ -1197,7 +1240,20 @@
return;
}
- getContentResolver().delete(contactUri, null, null);
+ final List<String> segments = contactUri.getPathSegments();
+ // Contains an Id.
+ final long uriContactId = Long.parseLong(segments.get(3));
+ int subscription = mSimContactsOperation.getSimSubscription(uriContactId);
+ if (subscription != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ ContentValues values = mSimContactsOperation
+ .getSimAccountValues(uriContactId);
+ int result = mSimContactsOperation.delete(values, subscription);
+ if (result == RESULT_SUCCESS) {
+ getContentResolver().delete(contactUri, null, null);
+ }
+ } else {
+ getContentResolver().delete(contactUri, null, null);
+ }
}
private void deleteMultipleContacts(Intent intent) {
diff --git a/src/com/android/contacts/ContactUtils.java b/src/com/android/contacts/ContactUtils.java
new file mode 100644
index 0000000..6241942
--- /dev/null
+++ b/src/com/android/contacts/ContactUtils.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2017, The Linux Foundation. All Rights Reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ */
+
+package com.android.contacts;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+import android.net.Uri;
+
+import com.android.contacts.model.account.SimAccountType;
+import com.android.contacts.SimContactsConstants;
+
+import android.os.IBinder;
+
+import android.os.ServiceManager;
+import com.android.internal.telephony.IIccPhoneBook;
+
+import java.util.ArrayList;
+/**
+ * Shared static SIM contact methods.
+ */
+public class ContactUtils {
+
+ private static final String TAG = "ContactUtils";
+ private static final int NAME_POS = 0;
+ private static final int NUMBER_POS = 1;
+ private static final int EMAIL_POS = 2;
+ private static final int ANR_POS = 3;
+ private static final int ADN_COUNT_POS = 0;
+ private static final int ADN_USED_POS = 1;
+ private static final int EMAIL_COUNT_POS = 2;
+ private static final int EMAIL_USED_POS = 3;
+ private static final int ANR_COUNT_POS = 4;
+ private static final int ANR_USED_POS = 5;
+ public static final int NAME_LENGTH_POS = 6;
+ public static final int NUMBER_LENGTH_POS = 7;
+ public static final int EMAIL_LENGTH_POS = 8;
+ public static final int ANR_LENGTH_POS = 9;
+
+ public final static int[] IC_SIM_PICTURE = {
+ R.drawable.ic_contact_picture_sim_1,
+ R.drawable.ic_contact_picture_sim_2,
+ };
+
+ public static int getSubscription(String accountType, String accountName) {
+ int subscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ if (accountType == null || accountName == null)
+ return subscription;
+ if (accountType.equals(SimAccountType.ACCOUNT_TYPE)) {
+ if (accountName.equals(SimContactsConstants.SIM_NAME)
+ || accountName.equals(SimContactsConstants.SIM_NAME_1)) {
+ subscription = SimContactsConstants.SLOT1;
+ } else if (accountName.equals(SimContactsConstants.SIM_NAME_2)) {
+ subscription = SimContactsConstants.SLOT2;
+ }
+ }
+ return subscription;
+ }
+
+ public static Account getAcount(Context c , int slot) {
+ Account account = null;
+ TelephonyManager tm = (TelephonyManager) c
+ .getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm.getPhoneCount() > 1) {
+ if (slot == SimContactsConstants.SLOT1) {
+ account = new Account(SimContactsConstants.SIM_NAME_1,
+ SimAccountType.ACCOUNT_TYPE);
+ } else if (slot == SimContactsConstants.SLOT2) {
+ account = new Account(SimContactsConstants.SIM_NAME_2,
+ SimAccountType.ACCOUNT_TYPE);
+ }
+ } else {
+ if (slot == SimContactsConstants.SLOT1)
+ account = new Account(SimContactsConstants.SIM_NAME,
+ SimAccountType.ACCOUNT_TYPE);
+ }
+ return account;
+ }
+
+ public static int[] getAdnRecordsCapacity(Context c, int slot) {
+ int subId = getActiveSubId(c, slot);
+ return getAdnRecordsCapacityForSubscriber(subId);
+ }
+
+ /**
+ * Returns the subscription's card can save anr or not.
+ */
+ public static boolean canSaveAnr(Context c, int slot) {
+ int adnCapacity[] = getAdnRecordsCapacity(c, slot);
+ return adnCapacity[ANR_COUNT_POS] > 0;
+ }
+
+ /**
+ * Returns the subscription's card can save email or not.
+ */
+ public static boolean canSaveEmail(Context c, int slot) {
+ int adnCapacity[] = getAdnRecordsCapacity(c, slot);
+ return adnCapacity[EMAIL_COUNT_POS] > 0;
+ }
+
+ public static int getOneSimAnrCount(Context c, int slot) {
+ int count = 0;
+ int adnCapacity[] = getAdnRecordsCapacity(c, slot);
+ int anrCount = adnCapacity[ANR_COUNT_POS];
+ int adnCount = adnCapacity[ADN_COUNT_POS];
+ if (adnCount > 0) {
+ count = anrCount % adnCount != 0 ? (anrCount / adnCount + 1)
+ : (anrCount / adnCount);
+ }
+ return count;
+ }
+
+ public static int getOneSimEmailCount(Context c, int slot) {
+ int count = 0;
+ int adnCapacity[] = getAdnRecordsCapacity(c, slot);
+ int emailCount = adnCapacity[EMAIL_COUNT_POS];
+ int adnCount = adnCapacity[ADN_COUNT_POS];
+ if (adnCount > 0) {
+ count = emailCount % adnCount != 0 ? (emailCount / adnCount + 1)
+ : (emailCount / adnCount);
+ }
+ return count;
+ }
+
+ public static int getSimFreeCount(Context context, int slot) {
+ int adnCapacity[] = getAdnRecordsCapacity(context, slot);
+ int count = adnCapacity[ADN_COUNT_POS]-adnCapacity[ADN_USED_POS];
+ Log.d(TAG, "spare adn:" + count);
+ return count;
+ }
+
+ public static int getSpareAnrCount(Context c, int slot) {
+ int adnCapacity[] = getAdnRecordsCapacity(c, slot);
+ int spareCount = adnCapacity[ANR_COUNT_POS]-adnCapacity[ANR_USED_POS];
+ Log.d(TAG, "spare anr:" + spareCount);
+ return spareCount;
+ }
+
+ public static int getSpareEmailCount(Context c, int slot) {
+ int adnCapacity[] = getAdnRecordsCapacity(c, slot);
+ int spareCount = adnCapacity[EMAIL_COUNT_POS]-adnCapacity[EMAIL_USED_POS];
+ Log.d(TAG, "spare email:" + spareCount);
+ return spareCount;
+ }
+
+ public static int getActiveSubId(Context c , int slot) {
+ SubscriptionInfo subInfoRecord = null;
+ try {
+ SubscriptionManager sm = SubscriptionManager.from(c);
+ subInfoRecord = sm.getActiveSubscriptionInfoForSimSlotIndex(slot);
+ } catch (Exception e) {
+ }
+ if (subInfoRecord != null)
+ return subInfoRecord.getSubscriptionId();
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+
+ public static int getActiveSlotId(Context c , int subId) {
+ SubscriptionInfo subInfoRecord = null;
+ try {
+ SubscriptionManager sm = SubscriptionManager.from(c);
+ subInfoRecord = sm.getActiveSubscriptionInfo(subId);
+ } catch (Exception e) {
+ }
+ if (subInfoRecord != null)
+ return subInfoRecord.getSimSlotIndex();
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+
+ public static boolean insertToPhone(String[] values, Context c, int slot) {
+ Account account = getAcount(c, slot);
+ final String name = values[NAME_POS];
+ final String phoneNumber = values[NUMBER_POS];
+ final String emailAddresses = values[EMAIL_POS];
+ final String anrs = values[ANR_POS];
+ final String[] emailAddressArray;
+ final String[] anrArray;
+ boolean success = true;
+ if (!TextUtils.isEmpty(emailAddresses)) {
+ emailAddressArray = emailAddresses.split(",");
+ } else {
+ emailAddressArray = null;
+ }
+ if (!TextUtils.isEmpty(anrs)) {
+ anrArray = anrs.split(SimContactsConstants.ANR_SEP);
+ } else {
+ anrArray = null;
+ }
+ Log.d(TAG, "insertToPhone: name= " + name + ", phoneNumber= "
+ + phoneNumber + ", emails= " + emailAddresses + ", anrs= "
+ + anrs + ", account= " + account);
+ final ArrayList<ContentProviderOperation> operationList =
+ new ArrayList<ContentProviderOperation>();
+ ContentProviderOperation.Builder builder = ContentProviderOperation
+ .newInsert(RawContacts.CONTENT_URI);
+ builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
+
+ if (account != null) {
+ builder.withValue(RawContacts.ACCOUNT_NAME, account.name);
+ builder.withValue(RawContacts.ACCOUNT_TYPE, account.type);
+ }
+ operationList.add(builder.build());
+
+ // do not allow empty value insert into database.
+ if (!TextUtils.isEmpty(name)) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+ builder.withValue(StructuredName.GIVEN_NAME, name);
+ operationList.add(builder.build());
+ }
+ if (!TextUtils.isEmpty(phoneNumber)) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
+ builder.withValue(Phone.NUMBER, phoneNumber);
+ builder.withValue(Data.IS_PRIMARY, 1);
+ operationList.add(builder.build());
+ }
+ if (anrArray != null) {
+ for (String anr : anrArray) {
+ if (!TextUtils.isEmpty(anr)) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ builder.withValue(Phone.TYPE, Phone.TYPE_HOME);
+ builder.withValue(Phone.NUMBER, anr);
+ operationList.add(builder.build());
+ }
+ }
+ }
+ if (emailAddressArray != null) {
+ for (String emailAddress : emailAddressArray) {
+ if (!TextUtils.isEmpty(emailAddress)) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+ builder.withValue(Email.ADDRESS, emailAddress);
+ operationList.add(builder.build());
+ }
+ }
+ }
+
+ try {
+ ContentProviderResult[] results =
+ c.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
+ for (ContentProviderResult result: results) {
+ if (result.uri == null) {
+ success = false;
+ break;
+ }
+ }
+ return success;
+ } catch (Exception e) {
+ Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ return false;
+ }
+ }
+
+ public static Uri insertToCard(Context context, String name, String number, String emails,
+ String anrNumber, int slot) {
+ return insertToCard(context, name, number, emails, anrNumber,slot, true);
+ }
+
+ public static Uri insertToCard(Context context, String name, String number, String emails,
+ String anrNumber, int slot, boolean insertToPhone) {
+ Uri result;
+ ContentValues mValues = new ContentValues();
+ mValues.clear();
+ mValues.put(SimContactsConstants.STR_TAG, name);
+ if (!TextUtils.isEmpty(number)) {
+ number = PhoneNumberUtils.stripSeparators(number);
+ mValues.put(SimContactsConstants.STR_NUMBER, number);
+ }
+ if (!TextUtils.isEmpty(emails)) {
+ mValues.put(SimContactsConstants.STR_EMAILS, emails);
+ }
+ if (!TextUtils.isEmpty(anrNumber)) {
+ anrNumber = anrNumber.replaceAll("[^0123456789PWN\\,\\;\\*\\#\\+\\:]", "");
+ mValues.put(SimContactsConstants.STR_ANRS, anrNumber);
+ }
+
+ SimContactsOperation mSimContactsOperation = new SimContactsOperation(context);
+ result = mSimContactsOperation.insert(mValues, slot);
+ if (result != null) {
+ if (insertToPhone) {
+ // we should import the contact to the sim account at the same
+ // time.
+ String[] value = new String[] { name, number, emails, anrNumber };
+ insertToPhone(value, context, slot);
+ }
+ } else {
+ Log.e(TAG, "export contact: [" + name + ", " + number + ", " + emails + "] to slot "
+ + slot + " failed");
+ }
+ return result;
+ }
+
+ //judge the max length of number,anr,email,name for sim contact
+ public static boolean isInValidData(String data, int maxLength) {
+ if (!TextUtils.isEmpty(data)) {
+ if (data.getBytes().length > data.length()) {
+ if (data.length() > ((maxLength - 1) / 2))
+ return true;
+ } else if (data.length() > maxLength)
+ return true;
+ }
+ return false;
+ }
+
+ /* capacity[0] is the max count of ADN
+ capacity[1] is the used count of ADN
+ capacity[2] is the max count of EMAIL
+ capacity[3] is the used count of EMAIL
+ capacity[4] is the max count of ANR
+ capacity[5] is the used count of ANR
+ capacity[6] is the max length of name
+ capacity[7] is the max length of number
+ capacity[8] is the max length of email
+ capacity[9] is the max length of anr
+ */
+ private static int[] getAdnRecordsCapacityForSubscriber(int subId) {
+ int defaultCapacity[] = { 0, 0, 0, 0, 0, 0, 14, 40, 40, 40 };
+ try {
+ IIccPhoneBook iccIpb = IIccPhoneBook.Stub
+ .asInterface(ServiceManager.getService("simphonebook"));
+ if (iccIpb != null) {
+ int[] capacity = iccIpb.getAdnRecordsCapacityForSubscriber(subId);
+ if (capacity != null)
+ return capacity;
+ }
+ }catch (Exception e){
+ Log.e(TAG," getAdnRecordsCapacityForSubscriber error = "+e.toString());
+ }
+ return defaultCapacity;
+ }
+}
diff --git a/src/com/android/contacts/DynamicShortcuts.java b/src/com/android/contacts/DynamicShortcuts.java
index 8130776..4999be8 100644
--- a/src/com/android/contacts/DynamicShortcuts.java
+++ b/src/com/android/contacts/DynamicShortcuts.java
@@ -15,6 +15,7 @@
*/
package com.android.contacts;
+import android.accounts.Account;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.job.JobInfo;
@@ -305,11 +306,16 @@
}
public ShortcutInfo getQuickContactShortcutInfo(long id, String lookupKey, String displayName) {
+ return getQuickContactShortcutInfo(id, lookupKey, displayName, null);
+ }
+
+ public ShortcutInfo getQuickContactShortcutInfo(long id, String lookupKey, String displayName,
+ Account account) {
final ShortcutInfo.Builder builder = builderForContactShortcut(id, lookupKey, displayName);
if (builder == null) {
return null;
}
- addIconForContact(id, lookupKey, displayName, builder);
+ addIconForContact(id, lookupKey, displayName, builder, account);
return builder.build();
}
@@ -336,9 +342,14 @@
private void addIconForContact(long id, String lookupKey, String displayName,
ShortcutInfo.Builder builder) {
+ addIconForContact(id, lookupKey, displayName, builder, null);
+ }
+
+ private void addIconForContact(long id, String lookupKey, String displayName,
+ ShortcutInfo.Builder builder, Account account) {
Bitmap bitmap = getContactPhoto(id);
if (bitmap == null) {
- bitmap = getFallbackAvatar(displayName, lookupKey);
+ bitmap = getFallbackAvatar(displayName, lookupKey, account);
}
final Icon icon;
if (BuildCompat.isAtLeastO()) {
@@ -414,7 +425,7 @@
return bitmap;
}
- private Bitmap getFallbackAvatar(String displayName, String lookupKey) {
+ private Bitmap getFallbackAvatar(String displayName, String lookupKey, Account account) {
// Use a circular icon if we're not on O or higher.
final boolean circularIcon = !BuildCompat.isAtLeastO();
@@ -425,7 +436,7 @@
request.scale = LetterTileDrawable.getAdaptiveIconScale();
}
final Drawable avatar = ContactPhotoManager.getDefaultAvatarDrawableForContact(
- mContext.getResources(), true, request);
+ mContext.getResources(), true, request, account);
final Bitmap result = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
// The avatar won't draw unless it thinks it is visible
avatar.setVisible(true, true);
diff --git a/src/com/android/contacts/ShortcutIntentBuilder.java b/src/com/android/contacts/ShortcutIntentBuilder.java
index 5ea6b7e..b1eba53 100644
--- a/src/com/android/contacts/ShortcutIntentBuilder.java
+++ b/src/com/android/contacts/ShortcutIntentBuilder.java
@@ -15,6 +15,7 @@
*/
package com.android.contacts;
+import android.accounts.Account;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -40,6 +41,7 @@
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
import androidx.core.graphics.drawable.IconCompat;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
@@ -62,12 +64,16 @@
private static final String[] CONTACT_COLUMNS = {
Contacts.DISPLAY_NAME,
Contacts.PHOTO_ID,
- Contacts.LOOKUP_KEY
+ Contacts.LOOKUP_KEY,
+ RawContacts.ACCOUNT_TYPE,
+ RawContacts.ACCOUNT_NAME,
};
private static final int CONTACT_DISPLAY_NAME_COLUMN_INDEX = 0;
private static final int CONTACT_PHOTO_ID_COLUMN_INDEX = 1;
private static final int CONTACT_LOOKUP_KEY_COLUMN_INDEX = 2;
+ private static final int CONTACT_ACCOUNT_TYPE_COLUMN_INDEX = 3;
+ private static final int CONTACT_ACCOUNT_NAME_COLUMN_INDEX = 4;
private static final String[] PHONE_COLUMNS = {
Phone.DISPLAY_NAME,
@@ -75,7 +81,9 @@
Phone.NUMBER,
Phone.TYPE,
Phone.LABEL,
- Phone.LOOKUP_KEY
+ Phone.LOOKUP_KEY,
+ RawContacts.ACCOUNT_TYPE,
+ RawContacts.ACCOUNT_NAME,
};
private static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 0;
@@ -84,6 +92,8 @@
private static final int PHONE_TYPE_COLUMN_INDEX = 3;
private static final int PHONE_LABEL_COLUMN_INDEX = 4;
private static final int PHONE_LOOKUP_KEY_COLUMN_INDEX = 5;
+ private static final int PHONE_ACCOUNT_TYPE_COLUMN_INDEX = 6;
+ private static final int PHONE_ACCOUNT_NAME_COLUMN_INDEX = 7;
private static final String[] PHOTO_COLUMNS = {
Photo.PHOTO,
@@ -157,7 +167,7 @@
protected String mLookupKey;
protected byte[] mBitmapData;
protected long mPhotoId;
-
+ private Account mAccount;
public LoadingAsyncTask(Uri uri) {
mUri = uri;
}
@@ -193,6 +203,8 @@
}
private final class ContactLoadingAsyncTask extends LoadingAsyncTask {
+ private Account mAccount;
+
public ContactLoadingAsyncTask(Uri uri) {
super(uri);
}
@@ -207,6 +219,12 @@
mDisplayName = cursor.getString(CONTACT_DISPLAY_NAME_COLUMN_INDEX);
mPhotoId = cursor.getLong(CONTACT_PHOTO_ID_COLUMN_INDEX);
mLookupKey = cursor.getString(CONTACT_LOOKUP_KEY_COLUMN_INDEX);
+ final String accountType = cursor
+ .getString(CONTACT_ACCOUNT_TYPE_COLUMN_INDEX);
+ final String accountName = cursor
+ .getString(CONTACT_ACCOUNT_NAME_COLUMN_INDEX);
+ if (accountType != null && accountName != null)
+ mAccount = new Account(accountName, accountType);
}
} finally {
cursor.close();
@@ -215,7 +233,8 @@
}
@Override
protected void onPostExecute(Void result) {
- createContactShortcutIntent(mUri, mContentType, mDisplayName, mLookupKey, mBitmapData);
+ createContactShortcutIntent(mUri, mContentType, mDisplayName,
+ mAccount, mLookupKey, mBitmapData);
}
}
@@ -224,6 +243,7 @@
private String mPhoneNumber;
private int mPhoneType;
private String mPhoneLabel;
+ private Account mAccount;
public PhoneNumberLoadingAsyncTask(Uri uri, String shortcutAction) {
super(uri);
@@ -243,6 +263,12 @@
mPhoneType = cursor.getInt(PHONE_TYPE_COLUMN_INDEX);
mPhoneLabel = cursor.getString(PHONE_LABEL_COLUMN_INDEX);
mLookupKey = cursor.getString(PHONE_LOOKUP_KEY_COLUMN_INDEX);
+ final String accountType = cursor
+ .getString(PHONE_ACCOUNT_TYPE_COLUMN_INDEX);
+ final String accountName = cursor
+ .getString(PHONE_ACCOUNT_NAME_COLUMN_INDEX);
+ if (accountType != null)
+ mAccount = new Account(accountName, accountType);
}
} finally {
cursor.close();
@@ -253,11 +279,12 @@
@Override
protected void onPostExecute(Void result) {
createPhoneNumberShortcutIntent(mUri, mDisplayName, mLookupKey, mBitmapData,
- mPhoneNumber, mPhoneType, mPhoneLabel, mShortcutAction);
+ mPhoneNumber, mPhoneType, mPhoneLabel, mAccount, mShortcutAction);
}
}
- private Drawable getPhotoDrawable(byte[] bitmapData, String displayName, String lookupKey) {
+ private Drawable getPhotoDrawable(byte[] bitmapData, String displayName, String lookupKey,
+ Account account) {
if (bitmapData != null) {
Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length, null);
return new BitmapDrawable(mContext.getResources(), bitmap);
@@ -269,12 +296,12 @@
request.scale = LetterTileDrawable.getAdaptiveIconScale();
}
return ContactPhotoManager.getDefaultAvatarDrawableForContact(mContext.getResources(),
- false, request);
+ false, request, account);
}
}
private void createContactShortcutIntent(Uri contactUri, String contentType, String displayName,
- String lookupKey, byte[] bitmapData) {
+ Account account, String lookupKey, byte[] bitmapData) {
Intent intent = null;
if (TextUtils.isEmpty(displayName)) {
displayName = mContext.getResources().getString(R.string.missing_name);
@@ -285,12 +312,12 @@
mContext.getSystemService(Context.SHORTCUT_SERVICE);
final DynamicShortcuts dynamicShortcuts = new DynamicShortcuts(mContext);
final ShortcutInfo shortcutInfo = dynamicShortcuts.getQuickContactShortcutInfo(
- contactId, lookupKey, displayName);
+ contactId, lookupKey, displayName, account);
if (shortcutInfo != null) {
intent = sm.createShortcutResultIntent(shortcutInfo);
}
}
- final Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey);
+ final Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey, account);
final Intent shortcutIntent = ImplicitIntentsUtil.getIntentForQuickContactLauncherShortcut(
mContext, contactUri);
@@ -312,8 +339,8 @@
private void createPhoneNumberShortcutIntent(Uri uri, String displayName, String lookupKey,
byte[] bitmapData, String phoneNumber, int phoneType, String phoneLabel,
- String shortcutAction) {
- final Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey);
+ Account account, String shortcutAction) {
+ final Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey, account);
final Bitmap icon;
final Uri phoneUri;
final String shortcutName;
diff --git a/src/com/android/contacts/SimContactsConstants.java b/src/com/android/contacts/SimContactsConstants.java
new file mode 100644
index 0000000..d196a4f
--- /dev/null
+++ b/src/com/android/contacts/SimContactsConstants.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015,2017, The Linux Foundation. All Rights Reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ */
+package com.android.contacts;
+
+
+public interface SimContactsConstants {
+
+ public static final String SIM_NAME = "SIM";
+ public static final String SIM_NAME_1 = "SIM1";
+ public static final String SIM_NAME_2 = "SIM2";
+ public static final int SLOT1 = 0;
+ public static final int SLOT2 = 1;
+ public static final String SLOT_KEY = "slot";
+ public static final String ACCOUNT_TYPE_SIM = "com.android.sim";
+ public static final String ACCOUNT_TYPE = "account_type";
+ public static final String ACCOUNT_NAME = "account_name";
+ public static final String ACCOUNT_DATA = "data_set";
+ public static final String STR_TAG = "tag";
+ public static final String STR_NUMBER = "number";
+ public static final String STR_EMAILS = "emails";
+ public static final String STR_ANRS = "anrs";
+ public static final String STR_NEW_TAG = "newTag";
+ public static final String STR_NEW_NUMBER = "newNumber";
+ public static final String STR_NEW_EMAILS = "newEmails";
+ public static final String STR_NEW_ANRS = "newAnrs";
+ public static final String ANR_SEP = ":";
+ public static final String EMAIL_SEP = ",";
+ public static final String SIM_URI = "content://icc/adn";
+ public static final String SIM_SUB_URI = "content://icc/adn/subId/";
+ public static final String RESULT_KEY = "result";
+ public static final String ACTION_MULTI_PICK_CONTACT =
+ "com.android.contacts.action.MULTI_PICK_CONTACT";
+ public static final String ACTION_MULTI_PICK =
+ "com.android.contacts.action.MULTI_PICK";
+ public static final String ACTION_MULTI_PICK_EMAIL =
+ "com.android.contacts.action.MULTI_PICK_EMAIL";
+}
+
+
diff --git a/src/com/android/contacts/SimContactsOperation.java b/src/com/android/contacts/SimContactsOperation.java
new file mode 100644
index 0000000..dc52af8
--- /dev/null
+++ b/src/com/android/contacts/SimContactsOperation.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2015-2017, The Linux Foundation. All Rights Reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ */
+
+package com.android.contacts;
+
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.CommonDataKinds;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.RawContacts;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.contacts.SimContactsConstants;
+import com.android.contacts.ContactUtils;
+
+public class SimContactsOperation {
+
+ private static final String TAG = "SimContactsOperation";
+ private static final boolean DBG = true;
+ public static final String[] CONTACT_PROJECTION = new String[] {
+ Contacts.Entity.RAW_CONTACT_ID,
+ Contacts.Entity.CONTACT_ID,
+ RawContacts.ACCOUNT_NAME,
+ RawContacts.ACCOUNT_TYPE,
+ Data.DATA1,
+ Data.DATA2,
+ Data.MIMETYPE,
+ };
+
+ private static final int CONTACT_COLUMN_RAW_ID = 0;
+ private static final int CONTACT_COLUMN_CONTACT_ID = 1;
+ private static final int CONTACT_COLUMN_ACCOUNT_NAME = 2;
+ private static final int CONTACT_COLUMN_ACCOUNT_TYPE = 3;
+ private static final int CONTACT_COLUMN_DATA = 4;
+ private static final int CONTACT_COLUMN_DATA_TYPE = 5;
+ private static final int CONTACT_COLUMN_DATA_MIMETYPE = 6;
+
+ private static Context mContext;
+ private ContentResolver mResolver;
+ private ContentValues mValues = new ContentValues();
+
+ public SimContactsOperation(Context context) {
+ mContext = context;
+ this.mResolver = context.getContentResolver();
+ }
+
+ public Uri insert(ContentValues values, int subscription) {
+
+ Uri uri = getContentUri(subscription);
+ if (uri == null)
+ return null;
+ String number = values.getAsString(SimContactsConstants.STR_NUMBER);
+ String anrs = values.getAsString(SimContactsConstants.STR_ANRS);
+ if (!TextUtils.isEmpty(anrs)) {
+ anrs = anrs.replaceAll("[^0123456789PWN\\,\\;\\*\\#\\+\\:]", "");
+ }
+ String emails = values.getAsString(SimContactsConstants.STR_EMAILS);
+ values.put(SimContactsConstants.STR_NUMBER,PhoneNumberUtils.stripSeparators(number));
+ values.put(SimContactsConstants.STR_ANRS,anrs);
+ values.put(SimContactsConstants.STR_EMAILS,emails);
+
+ Uri resultUri;
+ resultUri = mResolver.insert(uri,values);
+ return resultUri;
+ }
+
+ public int update(ContentValues values,int subscription) {
+ Uri uri = getContentUri(subscription);
+
+ int result = 0;
+ if (uri == null)
+ return result;
+ String oldNumber = values.getAsString(SimContactsConstants.STR_NUMBER);
+ String newNumber = values.getAsString(SimContactsConstants.STR_NEW_NUMBER);
+ String oldAnrs = values.getAsString(SimContactsConstants.STR_ANRS);
+ String newAnrs = values.getAsString(SimContactsConstants.STR_NEW_ANRS);
+ values.put(SimContactsConstants.STR_NUMBER,PhoneNumberUtils.stripSeparators(oldNumber));
+ values.put(SimContactsConstants.STR_NEW_NUMBER,PhoneNumberUtils.stripSeparators(newNumber));
+
+ String oldTag = values.getAsString(SimContactsConstants.STR_TAG);
+ String newTag = values.getAsString(SimContactsConstants.STR_NEW_TAG);
+ String oldEmails = values.getAsString(SimContactsConstants.STR_EMAILS);
+ String newEmails = values.getAsString(SimContactsConstants.STR_NEW_EMAILS);
+
+ if (TextUtils.isEmpty(newTag) && TextUtils.isEmpty(newNumber)
+ && TextUtils.isEmpty(newAnrs) && TextUtils.isEmpty(newEmails)){
+ return result;
+ }
+ if (!TextUtils.isEmpty(oldAnrs)) {
+ oldAnrs = oldAnrs.replaceAll("[^0123456789PWN\\,\\;\\*\\#\\+\\:]", "");
+ }
+ if (!TextUtils.isEmpty(newAnrs)) {
+ newAnrs = newAnrs.replaceAll("[^0123456789PWN\\,\\;\\*\\#\\+\\:]", "");
+ }
+ values.put(SimContactsConstants.STR_ANRS,oldAnrs);
+ values.put(SimContactsConstants.STR_NEW_ANRS,newAnrs);
+
+ result = mResolver.update(uri,values,null,null);
+ return result;
+
+ }
+
+ public int delete(ContentValues values, int subscription) {
+ Uri uri = getContentUri(subscription);
+
+ int result = 0;
+ if (uri == null)
+ return result;
+ StringBuilder buf = new StringBuilder();
+ String num = null;
+ String name = values.getAsString(SimContactsConstants.STR_TAG);
+ String number = values.getAsString(SimContactsConstants.STR_NUMBER);
+ String emails = values.getAsString(SimContactsConstants.STR_EMAILS);
+ String anrs = values.getAsString(SimContactsConstants.STR_ANRS);
+ if (number != null)
+ num = PhoneNumberUtils.stripSeparators(number);
+ if (anrs != null)
+ anrs = anrs.replaceAll("[^0123456789PWN\\,\\;\\*\\#\\+\\:]", "");
+ if (!TextUtils.isEmpty(name)) {
+ buf.append("tag='");
+ buf.append(name);
+ buf.append("'");
+ }
+ if (!TextUtils.isEmpty(num)) {
+ buf.append(" AND number='");
+ buf.append(num);
+ buf.append("'");
+ }
+ if (!TextUtils.isEmpty(emails)) {
+ buf.append(" AND emails='");
+ buf.append(emails);
+ buf.append("'");
+ }
+ if (!TextUtils.isEmpty(anrs)) {
+ buf.append(" AND anrs='");
+ buf.append(anrs);
+ buf.append("'");
+ }
+ result = mResolver.delete(uri,buf.toString(),null);
+ return result;
+ }
+
+ private Uri getContentUri(int slot) {
+ Uri uri = null;
+ int sub = ContactUtils.getActiveSubId(mContext, slot);
+ if (sub > 0) {
+ uri = Uri.parse(SimContactsConstants.SIM_SUB_URI + sub);
+ }
+ return uri;
+ }
+
+ private static Cursor setupContactCursor(long contactId) {
+ ContentResolver resolver = mContext.getContentResolver();
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
+ contactId);
+ Uri entityUri = Uri.withAppendedPath(contactUri,
+ Contacts.Entity.CONTENT_DIRECTORY);
+ Cursor cursor = null;
+ try {
+ cursor = resolver.query(entityUri,
+ CONTACT_PROJECTION, null, null, null);
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ } finally {
+ if (cursor != null && cursor.moveToFirst()) {
+ return cursor;
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ return null;
+ }
+ }
+
+ public static ContentValues getSimAccountValues(long contactId) {
+ ContentValues mValues = new ContentValues();
+ Cursor cursor = setupContactCursor(contactId);
+ StringBuilder anr = new StringBuilder();
+ StringBuilder email = new StringBuilder();
+ if (cursor == null) {
+ mValues.clear();
+ return mValues;
+ }
+
+ try {
+ if (cursor.getCount() == 0 || !cursor.moveToFirst()) {
+ cursor.close();
+ return mValues;
+ }
+ do {
+ String accountType = cursor
+ .getString(CONTACT_COLUMN_ACCOUNT_TYPE);
+ String accountName = cursor.getString(CONTACT_COLUMN_ACCOUNT_NAME);
+ String mimeType = cursor
+ .getString(CONTACT_COLUMN_DATA_MIMETYPE);
+ String data = cursor.getString(CONTACT_COLUMN_DATA);
+ String nameAndType = cursor.getString(CONTACT_COLUMN_DATA_TYPE);
+ mValues.put(SimContactsConstants.ACCOUNT_TYPE, accountType);
+ mValues.put(SimContactsConstants.ACCOUNT_NAME, accountName);
+ if (TextUtils.isEmpty(data))
+ continue;
+ if (SimContactsConstants.ACCOUNT_TYPE_SIM.equals(accountType)) {
+ if (mimeType.equals(StructuredName.CONTENT_ITEM_TYPE)) {
+ mValues.put(SimContactsConstants.STR_TAG, nameAndType);
+ } else if (mimeType.equals(Phone.CONTENT_ITEM_TYPE)) {
+ if (Integer.parseInt(nameAndType) == Phone.TYPE_MOBILE) {
+ mValues.put(SimContactsConstants.STR_NUMBER, data);
+ } else {
+ if (!TextUtils.isEmpty(anr.toString())) {
+ anr.append(SimContactsConstants.ANR_SEP);
+ }
+ anr.append(data);
+ }
+ } else if (mimeType.equals(Email.CONTENT_ITEM_TYPE)) {
+ if (!TextUtils.isEmpty(email.toString())) {
+ email.append(SimContactsConstants.EMAIL_SEP);
+ }
+ email.append(data);
+ }
+ }
+ } while (cursor.moveToNext());
+
+ if(!TextUtils.isEmpty(anr.toString()))
+ mValues.put(SimContactsConstants.STR_ANRS, anr.toString());
+ if (!TextUtils.isEmpty(email.toString()))
+ mValues.put(SimContactsConstants.STR_EMAILS, email.toString());
+ } catch (Exception e) {
+ Log.d(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ } finally {
+ cursor.close();
+ } Log.d(TAG,"getSimAccountValue: " + mValues.toString());
+ return mValues;
+ }
+
+ public static int getSimSubscription(long contactId) {
+ int subscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ Cursor cursor = setupContactCursor(contactId);
+ try {
+ if (cursor == null || cursor.getCount() == 0) {
+ return subscription;
+ }
+ String accountName = cursor.getString(cursor
+ .getColumnIndex(RawContacts.ACCOUNT_NAME));
+ String accountType = cursor.getString(cursor
+ .getColumnIndex(RawContacts.ACCOUNT_TYPE));
+ if (accountType == null || accountName == null) {
+ cursor.close();
+ return subscription;
+ }
+ if (SimContactsConstants.ACCOUNT_TYPE_SIM.equals(accountType)) {
+ subscription = ContactUtils.getSubscription(accountType,
+ accountName);
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return subscription;
+ }
+
+}
diff --git a/src/com/android/contacts/SimImportFragment.java b/src/com/android/contacts/SimImportFragment.java
index 6042939..babc3d3 100644
--- a/src/com/android/contacts/SimImportFragment.java
+++ b/src/com/android/contacts/SimImportFragment.java
@@ -43,6 +43,7 @@
import com.android.contacts.compat.CompatUtils;
import com.android.contacts.database.SimContactDao;
+import com.android.contacts.model.account.SimAccountType;
import com.android.contacts.editor.AccountHeaderPresenter;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.SimCard;
@@ -128,7 +129,18 @@
} else {
// Default may be null in which case the first account in the list will be selected
// after they are loaded.
- mAccountHeaderPresenter.setCurrentAccount(mPreferences.getDefaultAccount());
+ AccountWithDataSet account = mPreferences.getDefaultAccount();
+ // filter sim account here, uses the default google account instead
+ if (account != null && account.type != null && account.type.equals(
+ SimAccountType.ACCOUNT_TYPE)) {
+ List<AccountInfo> gAccount = mAccountTypeManager.getWritableGoogleAccounts();
+ if (gAccount.size() > 0) {
+ account = gAccount.get(0).getAccount();
+ } else {
+ account = AccountWithDataSet.getNullAccount();
+ }
+ }
+ mAccountHeaderPresenter.setCurrentAccount(account);
}
mAccountHeaderPresenter.setObserver(new AccountHeaderPresenter.Observer() {
@Override
@@ -437,6 +449,8 @@
return contact.getPhone();
} else if (contact.hasEmails()) {
return contact.getEmails()[0];
+ } else if(contact.hasAnrs()) {
+ return contact.getAnrs()[0];
} else {
// This isn't really possible because we skip empty SIM contacts during loading
return "";
@@ -461,7 +475,8 @@
protected ListenableFuture<LoaderResult> loadData() {
final ListenableFuture<List<Object>> future = Futures.<Object>allAsList(
mAccountTypeManager
- .filterAccountsAsync(AccountTypeManager.writableFilter()),
+ .filterAccountsAsync(AccountTypeManager.AccountFilter
+ .CONTACTS_WRITABLE_WITHOUT_SIM),
ContactsExecutors.getSimReadExecutor().<Object>submit(
new Callable<Object>() {
@Override
diff --git a/src/com/android/contacts/activities/MultiPickContactsActivity.java b/src/com/android/contacts/activities/MultiPickContactsActivity.java
new file mode 100644
index 0000000..a7b4d3f
--- /dev/null
+++ b/src/com/android/contacts/activities/MultiPickContactsActivity.java
@@ -0,0 +1,988 @@
+/**
+ * Copyright (C) 2013-2018, The Linux Foundation. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.contacts.activities;
+
+import android.accounts.Account;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.app.ProgressDialog;
+import android.content.ContentResolver;
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.ContentUris;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Handler;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.contacts.ContactUtils;
+import com.android.contacts.SimContactsConstants;
+import com.android.contacts.SimContactsOperation;
+import com.android.contacts.activities.RequestPermissionsActivity;
+import com.android.contacts.list.ContactsFragment;
+import com.android.contacts.list.ContactsPickMode;
+import com.android.contacts.list.OnCheckListActionListener;
+import com.android.contacts.R;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Set;
+
+public class MultiPickContactsActivity extends Activity implements
+ OnCheckListActionListener, View.OnClickListener, View.OnFocusChangeListener {
+
+ private final static String TAG = "MultiPickContactsActivity";
+ private final static boolean DEBUG = true;
+
+ private ContactsPickMode mPickMode;
+ private int mSelectedNums = 0;
+ private ContactsFragment mContactsFragment;
+ private boolean mSearchUiVisible = false;
+ // contains data ids
+ private Bundle mChoiceSet;
+ private Bundle mBackupChoiceSet;
+ private TextView mOKButton;
+ private ActionBar mActionBar;
+ private EditText mSearchView;
+ private ViewGroup mSearchViewContainer;
+ private View mSelectionContainer;
+ private Button mSelectionButton;
+ private MenuItem searchItem;
+ private SelectionMenu mSelectionMenu;
+ private Context mContext;
+ private ProgressDialog mProgressDialog;
+ private SimContactsOperation mSimContactsOperation;
+ private boolean mDelete = false;
+ private int mExportSub = -1;
+
+ private static final int ACCOUNT_TYPE_COLUMN_ID = 5;
+ private static final int ACCOUNT_NAME_COLUMN_ID = 6;
+ private static final int BUFFER_LENGTH = 400;
+
+ private static final int TOAST_EXPORT_FINISHED = 0;
+ // only for sim card is full
+ private static final int TOAST_SIM_CARD_FULL = 1;
+ // there is a case export is canceled by user
+ private static final int TOAST_EXPORT_CANCELED = 2;
+ // only for not have phone number or email address
+ private static final int TOAST_EXPORT_NO_PHONE_OR_EMAIL = 3;
+ // only for export failed in exporting progress
+ private static final int TOAST_SIM_EXPORT_FAILED = 4;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (RequestPermissionsActivity.startPermissionActivityIfNeeded(this)) {
+ return;
+ }
+ setContentView(R.layout.multi_pick_activity);
+ mChoiceSet = new Bundle();
+ mContext = getApplicationContext();
+ FragmentManager fragmentManager = getFragmentManager();
+ FragmentTransaction transaction = fragmentManager.beginTransaction();
+ mContactsFragment = (ContactsFragment) fragmentManager
+ .findFragmentByTag("tab-contacts");
+ if (mContactsFragment == null) {
+ mContactsFragment = new ContactsFragment();
+ transaction.add(R.id.pick_layout, mContactsFragment, "tab-contacts");
+ }
+ mContactsFragment.setCheckListListener(this);
+ transaction.commitAllowingStateLoss();
+ mActionBar = getActionBar();
+ mActionBar.setDisplayShowHomeEnabled(true);
+ mActionBar.setDisplayHomeAsUpEnabled(true);
+ mActionBar.setTitle(null);
+ mPickMode = ContactsPickMode.getInstance();
+ mPickMode.setMode(getIntent());
+ mDelete = getIntent().getBooleanExtra("delete", false);
+ mExportSub = getIntent().getIntExtra("exportSub", -1);
+ inflateSearchView();
+ mSimContactsOperation = new SimContactsOperation(this);
+ initResource();
+ mActionBar.setElevation(4 * getResources().getDisplayMetrics().density);
+ }
+
+ private void initResource() {
+ mOKButton = (TextView) findViewById(R.id.btn_ok);
+ mOKButton.setOnClickListener(this);
+ setOkStatus();
+ }
+
+ private void inflateSearchView() {
+ LayoutInflater inflater = LayoutInflater.from(mActionBar.getThemedContext());
+ mSearchViewContainer = (ViewGroup) inflater.inflate(R.layout.custom_pick_action_bar, null);
+ mSearchView = (EditText) mSearchViewContainer.findViewById(R.id.search_view);
+ mSearchView.setHintTextColor(getColor(R.color.searchbox_phone_hint_text_color));
+ mSearchView.setTextColor(getColor(R.color.searchbox_phone_text_color));
+ mSelectionContainer = inflater.inflate(R.layout.action_mode, null);
+ mSelectionButton = (Button) mSelectionContainer.findViewById(R.id.selection_menu);
+ mSelectionButton.setLayoutParams(new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT));
+ String countTitle = mContext.getResources().getString(R.string.contacts_selected,
+ mSelectedNums);
+ mSelectionButton.setText(countTitle);
+ mSelectionButton.setElevation(4 * getResources().getDisplayMetrics().density);
+ // Setup selection bar
+ if (mSelectionMenu == null) {
+ mSelectionMenu = new SelectionMenu(this, mSelectionButton,
+ new PopupList.OnPopupItemClickListener() {
+ @Override
+ public boolean onPopupItemClick(int itemId) {
+ if (itemId == SelectionMenu.SELECT_OR_DESELECT) {
+ setAllSelected();
+ }
+ return true;
+ }
+ });
+ mSelectionMenu.getPopupList().addItem(
+ SelectionMenu.SELECT_OR_DESELECT,
+ getString(R.string.menu_select_all));
+ }
+ mActionBar.setDisplayShowCustomEnabled(true);
+ configureSearchMode();
+ mSearchView.setHint(getString(R.string.enter_contact_name));
+ mSearchView.setFocusable(true);
+ mSearchView.setOnFocusChangeListener(this);
+ mSearchView.addTextChangedListener(new SearchTextWatcher());
+ }
+
+ private class SearchTextWatcher implements TextWatcher {
+ @Override
+ public void onTextChanged(CharSequence queryString, int start, int before, int count) {
+ updateState(queryString.toString());
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+ }
+
+ private void setAllSelected() {
+ boolean selectAll = false;
+ int checkNum = mChoiceSet.size();
+ int num = mContactsFragment.getListAdapter().getCount();
+ if (checkNum < num) {
+ selectAll = true;
+ }
+ mContactsFragment.setSelectedAll(selectAll);
+ }
+
+ private void addSelectionMenuPopupListItem(String countTitle) {
+ mSelectionMenu.getPopupList().addItem(SelectionMenu.SELECTED, countTitle);
+ boolean selectAll = true;
+ int checkNum = mChoiceSet.size();
+ int num = mContactsFragment.getListAdapter().getCount();
+ if (checkNum == num && num > 0) {
+ selectAll = false;
+ }
+ mSelectionMenu.getPopupList().addItem(
+ SelectionMenu.SELECT_OR_DESELECT,
+ getString(selectAll ? R.string.menu_select_all
+ : R.string.menu_select_none));
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.search_menu, menu);
+ searchItem = menu.findItem(R.id.menu_search);
+ searchItem.setVisible(!mSearchUiVisible);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_search:
+ mSearchUiVisible = true;
+ enterSearchMode();
+ configureSearchMode();
+ return true;
+ case android.R.id.home:
+ onBackPressed();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void configureSearchMode() {
+ TextView topDividerLine = (TextView) findViewById(R.id.multi_pick_top_divider);
+ if (mSearchUiVisible) {
+ topDividerLine.setVisibility(View.VISIBLE);
+ mActionBar.setHomeAsUpIndicator(R.drawable.quantum_ic_arrow_back_vd_theme_24);
+ int searchboxStatusBarColor = getColor(R.color.searchbox_phone_background_color);
+ ColorDrawable searchboxStatusBarDrawable = new ColorDrawable(searchboxStatusBarColor);
+ mActionBar.setBackgroundDrawable(searchboxStatusBarDrawable);
+ mSelectionContainer.setVisibility(View.GONE);
+ mSearchViewContainer.setVisibility(View.VISIBLE);
+ mActionBar.setCustomView(mSearchViewContainer, new ActionBar.LayoutParams(
+ ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.WRAP_CONTENT));
+ mSearchView.requestFocus();
+ } else {
+ topDividerLine.setVisibility(View.GONE);
+ mActionBar.setHomeAsUpIndicator(null);
+ int normalStatusBarColor = getColor(R.color.primary_color);
+ getActionBar().setBackgroundDrawable(new ColorDrawable(normalStatusBarColor));
+ mSearchViewContainer.setVisibility(View.GONE);
+ mSelectionContainer.setVisibility(View.VISIBLE);
+ mActionBar.setCustomView(mSelectionContainer, new ActionBar.LayoutParams(
+ ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.WRAP_CONTENT));
+ if (mSearchView != null && !TextUtils.isEmpty(mSearchView.getText().toString())) {
+ mSearchView.setText(null);
+ }
+ }
+ }
+
+ private void updateActionBar() {
+ mSelectedNums = (mChoiceSet.isEmpty() ? 0 : mChoiceSet.size());
+ String countTitle = mContext.getResources().getString(R.string.contacts_selected,
+ mSelectedNums);
+ mSelectionButton.setText(countTitle);
+ mSelectionMenu.getPopupList().clearItems();
+ addSelectionMenuPopupListItem(countTitle);
+ invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mSearchUiVisible) {
+ mSearchUiVisible = false;
+ exitSearchMode();
+ configureSearchMode();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public void onFocusChange(View view, boolean hasFocus) {
+ switch (view.getId()) {
+ case R.id.search_view: {
+ if (hasFocus) {
+ final InputMethodManager imm = (InputMethodManager) getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mSearchView.findFocus(), 0);
+ updateState(null);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mProgressDialog != null) {
+ mProgressDialog.cancel();
+ }
+ super.onDestroy();
+ }
+
+ private void updateState(String query) {
+ if (!TextUtils.isEmpty(query)) {
+ if (!mPickMode.isSearchMode()) {
+ mSearchUiVisible = true;
+ enterSearchMode();
+ configureSearchMode();
+ }
+ }
+ mContactsFragment.doFilter(query);
+ }
+
+ private void setOkStatus() {
+ if (0 == mChoiceSet.size()) {
+ mOKButton.setEnabled(false);
+ mOKButton.setTextColor(
+ mContext.getResources().getColor(R.color.ok_or_clear_button_disable_color));
+ } else {
+ mOKButton.setEnabled(true);
+ mOKButton.setTextColor(
+ mContext.getResources().getColor(R.color.ok_or_clear_button_normal_color));
+ }
+ }
+
+ private void enterSearchMode() {
+ mOKButton.setVisibility(View.GONE);
+ searchItem.setVisible(false);
+ mPickMode.enterSearchMode();
+ }
+
+ private void exitSearchMode() {
+ mOKButton.setVisibility(View.VISIBLE);
+ searchItem.setVisible(true);
+ mPickMode.exitSearchMode();
+ mContactsFragment.startQuery();
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id, Bundle bundle) {
+ switch (id) {
+ case R.id.dialog_delete_contact_confirmation: {
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.deleteConfirmation_title)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setMessage(R.string.ContactMultiDeleteConfirmation)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok,
+ new DeleteClickListener()).create();
+ }
+
+ }
+ return super.onCreateDialog(id, bundle);
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ switch (id) {
+ case R.id.btn_ok:
+ if (mPickMode.isSearchMode()) {
+ exitSearchMode();
+ }
+ if (mDelete) {
+ showDialog(R.id.dialog_delete_contact_confirmation);
+ } else if(mExportSub > -1) {
+ new ExportToSimThread().start();
+ } else {
+ Intent intent = new Intent();
+ Bundle bundle = new Bundle();
+ bundle.putBundle(SimContactsConstants.RESULT_KEY, mChoiceSet);
+ intent.putExtras(bundle);
+ this.setResult(RESULT_OK, intent);
+ finish();
+ }
+ break;
+ }
+ }
+
+ private void hideSoftKeyboard() {
+ // Hide soft keyboard, if visible
+ InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ inputMethodManager.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0);
+ }
+
+ protected static void log(String msg) {
+ if (DEBUG)
+ Log.d(TAG, msg);
+ }
+
+ private class DeleteClickListener implements DialogInterface.OnClickListener {
+ public void onClick(DialogInterface dialog, int which) {
+ CharSequence title = getString(R.string.delete_contacts_title);
+ CharSequence message = getString(R.string.delete_contacts_message);
+ Thread thread = new DeleteContactsThread();
+
+ DialogInterface.OnKeyListener keyListener = new DialogInterface.OnKeyListener() {
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_SEARCH:
+ case KeyEvent.KEYCODE_CALL:
+ return true;
+ default:
+ return false;
+ }
+ }
+ };
+
+ mProgressDialog = new ProgressDialog(MultiPickContactsActivity.this);
+ mProgressDialog.setTitle(title);
+ mProgressDialog.setMessage(message);
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
+ getString(R.string.btn_cancel), (OnClickListener) thread);
+ mProgressDialog.setOnCancelListener((OnCancelListener) thread);
+ mProgressDialog.setOnKeyListener(keyListener);
+ mProgressDialog.setProgress(0);
+ mProgressDialog.setMax(mChoiceSet.size());
+
+ // set dialog can not be canceled by touching outside area of
+ // dialog.
+ mProgressDialog.setCanceledOnTouchOutside(false);
+ mProgressDialog.show();
+
+ mOKButton.setEnabled(false);
+ mOKButton.setTextColor(mContext.getResources().getColor(
+ R.color.ok_or_clear_button_disable_color));
+
+ thread.start();
+ }
+ }
+
+ /**
+ * Delete contacts thread
+ */
+ public class DeleteContactsThread extends Thread
+ implements DialogInterface.OnCancelListener, DialogInterface.OnClickListener {
+ boolean mCanceled = false;
+ // Use to save the operated contacts.
+ private ArrayList<ContentProviderOperation> mOpsContacts = null;
+ public DeleteContactsThread() {
+ }
+
+ @Override
+ public void run() {
+ Bundle choiceSet = (Bundle) mChoiceSet.clone();
+ Set<String> keySet = choiceSet.keySet();
+ Iterator<String> iterator = keySet.iterator();
+ ContentProviderOperation cpo = null;
+ ContentProviderOperation.Builder builder = null;
+ // Current contact count we can delete.
+ int count = 0;
+ // The contacts we batch delete once.
+ final int BATCH_DELETE_CONTACT_NUMBER = 400;
+ mOpsContacts = new ArrayList<ContentProviderOperation>();
+ while (!mCanceled & iterator.hasNext()) {
+ String id = String.valueOf(iterator.next());
+ String[] value = choiceSet.getStringArray(id);
+ long longId = Long.parseLong(id);
+ String accountType = value[ACCOUNT_TYPE_COLUMN_ID];
+ String accountName = value[ACCOUNT_NAME_COLUMN_ID];
+ Uri uri = Uri.withAppendedPath(Contacts.CONTENT_URI, id);
+ //delete sim contacts first
+ if (accountType != null && accountType
+ .equals(SimContactsConstants.ACCOUNT_TYPE_SIM)) {
+ int subscription = ContactUtils.getSubscription(
+ accountType, accountName);
+ ContentValues values = mSimContactsOperation
+ .getSimAccountValues(longId);
+ int result = mSimContactsOperation.delete(values,
+ subscription);
+ if (result == 0) {
+ mProgressDialog.incrementProgressBy(1);
+ continue;
+ }
+ }
+ builder = ContentProviderOperation.newDelete(uri);
+ cpo = builder.build();
+ mOpsContacts.add(cpo);
+ mProgressDialog.incrementProgressBy(1);
+ if (count % BATCH_DELETE_CONTACT_NUMBER == 0) {
+ batchDelete();
+ }
+ count++;
+ }
+ batchDelete();
+ mOpsContacts = null;
+ finish();
+ }
+
+ /**
+ * Batch delete contacts more efficient than one by one.
+ */
+ private void batchDelete() {
+ try {
+ mContext.getContentResolver().applyBatch(
+ android.provider.ContactsContract.AUTHORITY, mOpsContacts);
+ mOpsContacts.clear();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void onCancel(DialogInterface dialogInterface) {
+ // Cancel delete operate.
+ mCanceled = true;
+ Toast.makeText(mContext, R.string.delete_termination, Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ if (i == DialogInterface.BUTTON_NEGATIVE) {
+ mCanceled = true;
+ }
+ }
+ }
+
+ /**
+ * A thread that export contacts to sim card
+ */
+ public class ExportToSimThread extends Thread {
+ private int slot;
+ private boolean canceled = false;
+ private int freeSimCount = 0;
+ private ArrayList<ContentProviderOperation> operationList =
+ new ArrayList<ContentProviderOperation>();
+ private Account account;
+ final int BATCH_INSERT_NUMBER = 400;
+
+ public ExportToSimThread() {
+ slot = ContactUtils.getActiveSlotId(mContext, mExportSub);
+ account = ContactUtils.getAcount(mContext, slot);
+ showExportProgressDialog();
+ }
+
+ @Override
+ public void run() {
+ boolean isSimCardFull = false;
+ // in case export is stopped, record the count of inserted
+ // successfully
+ int insertCount = 0;
+ freeSimCount = ContactUtils.getSimFreeCount(mContext, slot);
+ boolean canSaveAnr = ContactUtils.canSaveAnr(mContext, slot);
+ boolean canSaveEmail = ContactUtils.canSaveEmail(mContext, slot);
+ int emailCountInOneSimContact = ContactUtils
+ .getOneSimEmailCount(mContext, slot);
+ int phoneCountInOneSimContact = ContactUtils.getOneSimAnrCount(
+ mContext, slot) + 1;
+ int emptyAnr = ContactUtils.getSpareAnrCount(mContext, slot);
+ int emptyEmail = ContactUtils.getSpareEmailCount(mContext, slot);
+ int emptyNumber = freeSimCount + emptyAnr;
+
+ Log.d(TAG, "freeSimCount = " + freeSimCount);
+ Bundle choiceSet = (Bundle) mChoiceSet.clone();
+ Set<String> set = choiceSet.keySet();
+ Iterator<String> i = set.iterator();
+ while (i.hasNext() && !canceled) {
+ String id = String.valueOf(i.next());
+ String name = "";
+ ArrayList<String> arrayNumber = new ArrayList<String>();
+ ArrayList<String> arrayEmail = new ArrayList<String>();
+
+ Uri dataUri = Uri.withAppendedPath(
+ ContentUris.withAppendedId(Contacts.CONTENT_URI,
+ Long.parseLong(id)),
+ Contacts.Data.CONTENT_DIRECTORY);
+ final String[] projection = new String[] { Contacts._ID,
+ Contacts.Data.MIMETYPE, Contacts.Data.DATA1, };
+ Cursor c = mContext.getContentResolver().query(dataUri,
+ projection, null, null, null);
+ try {
+ if (c != null && c.moveToFirst()) {
+ do {
+ String mimeType = c.getString(1);
+ if (StructuredName.CONTENT_ITEM_TYPE
+ .equals(mimeType)) {
+ name = c.getString(2);
+ }
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ String number = c.getString(2);
+ if (!TextUtils.isEmpty(number)
+ && emptyNumber-- > 0) {
+ arrayNumber.add(number);
+ }
+ }
+ if (canSaveEmail) {
+ if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ String email = c.getString(2);
+ if (!TextUtils.isEmpty(email)
+ && emptyEmail-- > 0) {
+ arrayEmail.add(email);
+ }
+ }
+ }
+ } while (c.moveToNext());
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ if (freeSimCount > 0 && 0 == arrayNumber.size()
+ && 0 == arrayEmail.size()) {
+ mToastHandler.sendMessage(mToastHandler.obtainMessage(
+ TOAST_EXPORT_NO_PHONE_OR_EMAIL, name));
+ continue;
+ }
+
+ int nameCount = (name != null && !name.equals("")) ? 1 : 0;
+ int groupNumCount = (arrayNumber.size() % phoneCountInOneSimContact) != 0 ?
+ (arrayNumber.size() / phoneCountInOneSimContact + 1)
+ : (arrayNumber.size() / phoneCountInOneSimContact);
+ int groupEmailCount = emailCountInOneSimContact == 0 ? 0
+ : ((arrayEmail.size() % emailCountInOneSimContact) != 0 ? (arrayEmail
+ .size() / emailCountInOneSimContact + 1)
+ : (arrayEmail.size() / emailCountInOneSimContact));
+ // recalute the group when spare anr is not enough
+ if (canSaveAnr && emptyAnr >= 0 && emptyAnr <= groupNumCount) {
+ groupNumCount = arrayNumber.size() - emptyAnr;
+ }
+ int groupCount = Math.max(groupEmailCount,
+ Math.max(nameCount, groupNumCount));
+
+ Uri result = null;
+ if (DEBUG) {
+ Log.d(TAG, "GroupCount = " + groupCount);
+ }
+ for (int k = 0; k < groupCount; k++) {
+ if (freeSimCount > 0) {
+ String num = arrayNumber.size() > 0 ? arrayNumber
+ .remove(0) : null;
+ StringBuilder anrNum = new StringBuilder();
+ StringBuilder email = new StringBuilder();
+ if (canSaveAnr) {
+ for (int j = 1; j < phoneCountInOneSimContact; j++) {
+ if (arrayNumber.size() > 0 && emptyAnr-- > 0) {
+ String s = arrayNumber.remove(0);
+ anrNum.append(s);
+ anrNum.append(SimContactsConstants.ANR_SEP);
+ }
+ }
+ }
+ if (canSaveEmail) {
+ for (int j = 0; j < emailCountInOneSimContact; j++) {
+ if (arrayEmail.size() > 0) {
+ String s = arrayEmail.remove(0);
+ email.append(s);
+ email.append(SimContactsConstants.EMAIL_SEP);
+ }
+ }
+ }
+
+ result = ContactUtils.insertToCard(mContext, name,
+ num, email.toString(), anrNum.toString(),
+ slot, false);
+
+ if (null == result) {
+ // Failed to insert to SIM card
+ int anrNumber = 0;
+ if (!TextUtils.isEmpty(anrNum)) {
+ anrNumber += anrNum.toString().split(
+ SimContactsConstants.ANR_SEP).length;
+ }
+ // reset emptyNumber and emptyAnr to the value
+ // before
+ // the insert operation
+ emptyAnr += anrNumber;
+ emptyNumber += anrNumber;
+ if (!TextUtils.isEmpty(num)) {
+ emptyNumber++;
+ }
+
+ if (!TextUtils.isEmpty(email)) {
+ // reset emptyEmail to the value before the
+ // insert
+ // operation
+ emptyEmail += email.toString().split(
+ SimContactsConstants.EMAIL_SEP).length;
+ }
+
+ mToastHandler.sendMessage(mToastHandler
+ .obtainMessage(
+ TOAST_SIM_EXPORT_FAILED,
+ new String[] { name, num,
+ email.toString() }));
+
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Exported contact [" + name + ", "
+ + id + "] to slot " + slot);
+ }
+ insertCount++;
+ freeSimCount--;
+ batchInsert(name, num, anrNum.toString(),
+ email.toString());
+ }
+ } else {
+ isSimCardFull = true;
+ mToastHandler.sendMessage(mToastHandler.obtainMessage(
+ TOAST_SIM_CARD_FULL, insertCount, 0));
+ break;
+ }
+ }
+
+ if (isSimCardFull) {
+ break;
+ }
+ }
+
+ if (operationList.size() > 0) {
+ try {
+ mContext.getContentResolver().applyBatch(
+ android.provider.ContactsContract.AUTHORITY,
+ operationList);
+ } catch (Exception e) {
+ Log.e(TAG,
+ String.format("%s: %s", e.toString(),
+ e.getMessage()));
+ } finally {
+ operationList.clear();
+ }
+ }
+
+ if (!isSimCardFull) {
+ // if canceled, show toast indicating export is interrupted.
+ if (canceled) {
+ mToastHandler.sendMessage(mToastHandler.obtainMessage(TOAST_EXPORT_CANCELED,
+ insertCount, 0));
+ } else {
+ mToastHandler.sendEmptyMessage(TOAST_EXPORT_FINISHED);
+ }
+ }
+ finish();
+ }
+
+ private void batchInsert(String name, String phoneNumber, String anrs,
+ String emailAddresses) {
+ final String[] emailAddressArray;
+ final String[] anrArray;
+ if (!TextUtils.isEmpty(emailAddresses)) {
+ emailAddressArray = emailAddresses.split(",");
+ } else {
+ emailAddressArray = null;
+ }
+ if (!TextUtils.isEmpty(anrs)) {
+ anrArray = anrs.split(SimContactsConstants.ANR_SEP);
+ } else {
+ anrArray = null;
+ }
+ Log.d(TAG, "insertToPhone: name= " + name + ", phoneNumber= " + phoneNumber
+ + ", emails= " + emailAddresses + ", anrs= " + anrs + ", account= " + account);
+ ContentProviderOperation.Builder builder = ContentProviderOperation
+ .newInsert(RawContacts.CONTENT_URI);
+ builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
+
+ int ref = operationList.size();
+ if (account != null) {
+ builder.withValue(RawContacts.ACCOUNT_NAME, account.name);
+ builder.withValue(RawContacts.ACCOUNT_TYPE, account.type);
+ }
+ operationList.add(builder.build());
+ // do not allow empty value insert into database.
+ if (!TextUtils.isEmpty(name)) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, ref);
+ builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+ builder.withValue(StructuredName.GIVEN_NAME, name);
+ operationList.add(builder.build());
+ }
+
+ if (!TextUtils.isEmpty(phoneNumber)) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Phone.RAW_CONTACT_ID, ref);
+ builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
+ builder.withValue(Phone.NUMBER, phoneNumber);
+ builder.withValue(Data.IS_PRIMARY, 1);
+ operationList.add(builder.build());
+ }
+
+ if (anrArray != null) {
+ for (String anr : anrArray) {
+ if (!TextUtils.isEmpty(anr)) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Phone.RAW_CONTACT_ID, ref);
+ builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ builder.withValue(Phone.TYPE, Phone.TYPE_HOME);
+ builder.withValue(Phone.NUMBER, anr);
+ operationList.add(builder.build());
+ }
+ }
+ }
+
+ if (emailAddressArray != null) {
+ for (String emailAddress : emailAddressArray) {
+ if (!TextUtils.isEmpty(emailAddress)) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Email.RAW_CONTACT_ID, ref);
+ builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+ builder.withValue(Email.TYPE, Email.TYPE_MOBILE);
+ builder.withValue(Email.ADDRESS, emailAddress);
+ operationList.add(builder.build());
+ }
+ }
+ }
+
+ if (BATCH_INSERT_NUMBER - operationList.size() < 10) {
+ try {
+ mContext.getContentResolver().applyBatch(
+ android.provider.ContactsContract.AUTHORITY,
+ operationList);
+ } catch (Exception e) {
+ Log.e(TAG,
+ String.format("%s: %s", e.toString(),
+ e.getMessage()));
+ } finally {
+ operationList.clear();
+ }
+ }
+ }
+
+ private Handler mToastHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ int exportCount = 0;
+ switch (msg.what) {
+ case TOAST_EXPORT_FINISHED:
+ Toast.makeText(mContext, R.string.export_finished,
+ Toast.LENGTH_SHORT).show();
+ break;
+ case TOAST_SIM_CARD_FULL:
+ exportCount = msg.arg1;
+ Toast.makeText(
+ mContext,
+ mContext.getString(
+ R.string.export_sim_card_full, exportCount),
+ Toast.LENGTH_SHORT).show();
+ break;
+ case TOAST_EXPORT_CANCELED:
+ exportCount = msg.arg1;
+ Toast.makeText(
+ mContext,
+ mContext.getString(R.string.export_cancelled,
+ String.valueOf(exportCount)),
+ Toast.LENGTH_SHORT).show();
+ break;
+ case TOAST_EXPORT_NO_PHONE_OR_EMAIL:
+ String name = (String) msg.obj;
+ Toast.makeText(
+ mContext,
+ mContext.getString(
+ R.string.export_no_phone_or_email, name),
+ Toast.LENGTH_SHORT).show();
+ break;
+ case TOAST_SIM_EXPORT_FAILED:
+ String[] contactInfos = (String[]) msg.obj;
+ if (contactInfos != null && contactInfos.length == 3) {
+ String toastS = mContext.getString(
+ R.string.sim_contact_export_failed,
+ contactInfos[0] == null ? "" : contactInfos[0],
+ contactInfos[1] == null ? "" : contactInfos[1],
+ contactInfos[2] == null ? "" : contactInfos[2]);
+
+ Toast.makeText(mContext, toastS,
+ Toast.LENGTH_SHORT).show();
+ }
+ break;
+ }
+ }
+ };
+
+ public void showExportProgressDialog(){
+ mProgressDialog = new ProgressDialog(MultiPickContactsActivity.this);
+ mProgressDialog.setTitle(R.string.export_to_sim);
+ mProgressDialog.setOnCancelListener(new OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ Log.d(TAG, "Cancel exporting contacts");
+ canceled = true;
+ }
+ });
+ mProgressDialog.setMessage(mContext.getString(R.string.exporting));
+ mProgressDialog.setMax(mChoiceSet.size());
+ mProgressDialog.setCanceledOnTouchOutside(false);
+
+ // add a cancel button to let user cancel explicitly.
+ mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
+ mContext.getString(R.string.progressdialog_cancel),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Log.d(TAG, "Cancel exporting contacts by click button");
+ canceled = true;
+ }
+ });
+
+ mProgressDialog.show();
+ mOKButton.setEnabled(false);
+ mOKButton.setTextColor(
+ mContext.getResources().getColor(R.color.ok_or_clear_button_disable_color));
+ }
+ }
+
+ @Override
+ public boolean onContainsKey(String key) {
+ return mChoiceSet.containsKey(key);
+ }
+
+ @Override
+ public void putValue(String key, String[] value) {
+ mChoiceSet.putStringArray(key, value);
+ setOkStatus();
+ }
+
+ @Override
+ public void onRemove(String key) {
+ mChoiceSet.remove(key);
+ setOkStatus();
+ }
+
+ @Override
+ public void onClear() {
+ mChoiceSet.clear();
+ setOkStatus();
+ }
+
+ @Override
+ public void onHideSoftKeyboard() {
+ hideSoftKeyboard();
+ }
+
+ @Override
+ public void onUpdateActionBar() {
+ updateActionBar();
+ }
+
+ @Override
+ public void exitSearch() {
+ if (mPickMode.isSearchMode()) {
+ mSearchUiVisible = false;
+ exitSearchMode();
+ configureSearchMode();
+ }
+ }
+}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 243ec00..112fa1b 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -736,7 +736,7 @@
fragment.updateStatus(mProviderStatus);
}
if (!transaction.isEmpty()) {
- transaction.commit();
+ transaction.commitAllowingStateLoss();
fragmentManager.executePendingTransactions();
}
diff --git a/src/com/android/contacts/activities/PopupList.java b/src/com/android/contacts/activities/PopupList.java
new file mode 100644
index 0000000..09c0853
--- /dev/null
+++ b/src/com/android/contacts/activities/PopupList.java
@@ -0,0 +1,231 @@
+/*
+ Copyright (c) 2014, 2016, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.contacts.activities;
+
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import com.android.contacts.R;
+
+public class PopupList {
+ private final Context mContext;
+ private final View mAnchorView;
+ private final ArrayList<Item> mItems = new ArrayList<Item>();
+
+ private PopupWindow mPopupWindow;
+ private ListView mContentList;
+ private OnPopupItemClickListener mOnPopupItemClickListener;
+
+ private int mPopupOffsetX;
+ private int mPopupOffsetY;
+ private int mPopupWidth;
+ private int mPopupHeight;
+
+ private final PopupWindow.OnDismissListener mOnDismissListener =
+ new PopupWindow.OnDismissListener() {
+ @Override
+ public void onDismiss() {
+ if (mPopupWindow == null) return;
+ mPopupWindow = null;
+ ViewTreeObserver observer = mAnchorView.getViewTreeObserver();
+ if (observer.isAlive()) {
+ observer.removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
+ }
+ }
+ };
+
+ private final OnItemClickListener mOnItemClickListener =
+ new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (mPopupWindow == null) return;
+ mPopupWindow.dismiss();
+ if (mOnPopupItemClickListener != null) {
+ mOnPopupItemClickListener.onPopupItemClick((int) id);
+ }
+ }
+ };
+
+ private final OnGlobalLayoutListener mOnGlobalLayoutListener =
+ new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (mPopupWindow == null) return;
+ updatePopupLayoutParams();
+ // Need to update the position of the popup window
+ mPopupWindow.update(mAnchorView,
+ mPopupOffsetX, mPopupOffsetY, mPopupWidth, mPopupHeight);
+ }
+ };
+
+ public PopupList(Context context, View anchorView) {
+ mContext = context;
+ mAnchorView = anchorView;
+ }
+
+ public void setOnPopupItemClickListener(OnPopupItemClickListener listener) {
+ mOnPopupItemClickListener = listener;
+ }
+
+ public void addItem(int id, String title) {
+ mItems.add(new Item(id, title));
+ }
+
+ public void clearItems() {
+ mItems.clear();
+ }
+
+ public boolean isShowing() {
+ if (mPopupWindow != null) {
+ return mPopupWindow.isShowing();
+ }
+ return false;
+ }
+
+ public void show() {
+ if (mPopupWindow != null) return;
+ mAnchorView.getViewTreeObserver()
+ .addOnGlobalLayoutListener(mOnGlobalLayoutListener);
+ mPopupWindow = createPopupWindow();
+ updatePopupLayoutParams();
+ mPopupWindow.setWidth(mPopupWidth);
+ mPopupWindow.setHeight(mPopupHeight);
+ mPopupWindow.showAsDropDown(mAnchorView, mPopupOffsetX, mPopupOffsetY);
+ }
+
+ public void dismiss() {
+ if (mPopupWindow != null)
+ mPopupWindow.dismiss();
+ }
+
+ private void updatePopupLayoutParams() {
+ ListView content = mContentList;
+ PopupWindow popup = mPopupWindow;
+
+ Rect p = new Rect();
+ popup.getBackground().getPadding(p);
+
+ int maxHeight = mPopupWindow.getMaxAvailableHeight(mAnchorView) - p.top - p.bottom;
+ mContentList.measure(
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
+ mPopupWidth = content.getMeasuredWidth() + p.top + p.bottom;
+ mPopupHeight = Math.min(maxHeight, content.getMeasuredHeight() + p.left + p.right);
+ mPopupOffsetX = -p.left;
+ mPopupOffsetY = -mAnchorView.getHeight()-p.top;
+ }
+
+ private PopupWindow createPopupWindow() {
+ PopupWindow popup = new PopupWindow(mContext);
+ popup.setOnDismissListener(mOnDismissListener);
+ popup.setBackgroundDrawable(new ColorDrawable(Color.WHITE));
+
+ mContentList = new ListView(mContext, null,
+ android.R.attr.dropDownListViewStyle);
+ mContentList.setBackgroundResource(R.color.select_popup_button_background);
+ mContentList.setAdapter(new ItemDataAdapter());
+ mContentList.setOnItemClickListener(mOnItemClickListener);
+ popup.setElevation(24);
+ popup.setContentView(mContentList);
+ popup.setFocusable(true);
+ popup.setOutsideTouchable(true);
+
+ return popup;
+ }
+
+ public Item findItem(int id) {
+ for (Item item : mItems) {
+ if (item.id == id) return item;
+ }
+ return null;
+ }
+
+ public static interface OnPopupItemClickListener {
+ public boolean onPopupItemClick(int itemId);
+ }
+
+ public static class Item {
+ public final int id;
+ public String title;
+
+ public Item(int id, String title) {
+ this.id = id;
+ this.title = title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+ }
+
+ private class ItemDataAdapter extends BaseAdapter {
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mItems.get(position).id;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(mContext)
+ .inflate(R.layout.popup_list_item, null);
+ }
+ TextView text = (TextView) convertView.findViewById(R.id.popup_list_title);
+ text.setText(mItems.get(position).title);
+ return convertView;
+ }
+ }
+}
diff --git a/src/com/android/contacts/activities/SelectionMenu.java b/src/com/android/contacts/activities/SelectionMenu.java
new file mode 100644
index 0000000..46fed4e
--- /dev/null
+++ b/src/com/android/contacts/activities/SelectionMenu.java
@@ -0,0 +1,86 @@
+/*
+ Copyright (c) 2014, 2016, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.android.contacts.activities;
+
+import com.android.contacts.activities.PopupList;
+import com.android.contacts.R;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.Button;
+
+public class SelectionMenu implements View.OnClickListener {
+ private final Context mContext;
+ private final Button mButton;
+ private final PopupList mPopupList;
+ public static final int SELECTED = 0;
+ public static final int SELECT_OR_DESELECT = 1;
+
+ public SelectionMenu(Context context, Button button,
+ PopupList.OnPopupItemClickListener listener) {
+ mContext = context;
+ mButton = button;
+ mPopupList = new PopupList(context, mButton);
+ mPopupList.setOnPopupItemClickListener(listener);
+ mButton.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ mPopupList.show();
+ }
+
+ public void showPopupList() {
+ mPopupList.show();
+ }
+
+ public boolean isPopupListShow() {
+ return mPopupList.isShowing();
+ }
+
+ public void dismiss() {
+ mPopupList.dismiss();
+ }
+
+ public void updateSelectAllMode(boolean inSelectAllMode) {
+ PopupList.Item item = mPopupList.findItem(SELECT_OR_DESELECT);
+ if (item != null) {
+ item.setTitle(mContext.getString(
+ inSelectAllMode ? R.string.menu_select_none : R.string.menu_select_all));
+ }
+ }
+
+ public void setTitle(CharSequence title) {
+ mButton.setText(title);
+ }
+
+ public PopupList getPopupList(){
+ return mPopupList;
+ }
+}
diff --git a/src/com/android/contacts/database/SimContactDaoImpl.java b/src/com/android/contacts/database/SimContactDaoImpl.java
index 5ba6bd5..36ad9f2 100644
--- a/src/com/android/contacts/database/SimContactDaoImpl.java
+++ b/src/com/android/contacts/database/SimContactDaoImpl.java
@@ -82,6 +82,7 @@
public static String NAME = "name";
public static String NUMBER = "number";
public static String EMAILS = "emails";
+ public static String ANRS = "anrs";
private final Context mContext;
private final ContentResolver mResolver;
@@ -292,7 +293,7 @@
final int colName = cursor.getColumnIndex(NAME);
final int colNumber = cursor.getColumnIndex(NUMBER);
final int colEmails = cursor.getColumnIndex(EMAILS);
-
+ final int colAnrs = cursor.getColumnIndex(ANRS);
final ArrayList<SimContact> result = new ArrayList<>();
while (cursor.moveToNext()) {
@@ -300,10 +301,14 @@
final String name = cursor.getString(colName);
final String number = cursor.getString(colNumber);
final String emails = cursor.getString(colEmails);
-
- final SimContact contact = new SimContact(id, name, number, parseEmails(emails));
+ String anrs = "";
+ if (colAnrs >=0 )
+ anrs = cursor.getString(colAnrs);
+ final SimContact contact = new SimContact(id, name, number, parseEmails(emails),
+ parseAnrs(anrs));
// Only include contact if it has some useful data
- if (contact.hasName() || contact.hasPhone() || contact.hasEmails()) {
+ if (contact.hasName() || contact.hasPhone() || contact.hasEmails()
+ || contact.hasAnrs()) {
result.add(contact);
}
}
@@ -396,6 +401,10 @@
return !TextUtils.isEmpty(emails) ? emails.split(",") : null;
}
+ private String[] parseAnrs(String anrs) {
+ return !TextUtils.isEmpty(anrs) ? anrs.split(":") : null;
+ }
+
private boolean hasTelephony() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
}
diff --git a/src/com/android/contacts/drawer/DrawerAdapter.java b/src/com/android/contacts/drawer/DrawerAdapter.java
index 0c8423a..ee09d20 100644
--- a/src/com/android/contacts/drawer/DrawerAdapter.java
+++ b/src/com/android/contacts/drawer/DrawerAdapter.java
@@ -272,7 +272,6 @@
}
final ContactListFilter account = item.account;
final TextView textView = ((TextView) result.findViewById(R.id.title));
- textView.setText(account.accountName);
final boolean activated = account.equals(mSelectedAccount)
&& mSelectedView == ContactsView.ACCOUNT_VIEW;
textView.setTextAppearance(mActivity, activated
@@ -283,6 +282,11 @@
mAccountDisplayFactory.getAccountDisplayInfoFor(item.account);
icon.setScaleType(ImageView.ScaleType.FIT_CENTER);
icon.setImageDrawable(displayableAccount.getIcon());
+ if (account.accountName != null) {
+ textView.setText(account.accountName);
+ }else {
+ textView.setText(displayableAccount.getNameLabel());
+ }
result.setTag(account);
result.setActivated(activated);
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index a8e3cd8..4cef614 100755
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -1783,6 +1783,11 @@
getEditorActivity().changePhoto(getPhotoMode());
}
+ @Override
+ public void onClearPhotoCache() {
+ mUpdatedPhotos.remove(String.valueOf(mPhotoRawContactId));
+ }
+
private int getPhotoMode() {
return getContent().isWritablePhotoSet() ? PhotoActionPopup.Modes.WRITE_ABLE_PHOTO
: PhotoActionPopup.Modes.NO_PHOTO;
diff --git a/src/com/android/contacts/editor/LabeledEditorView.java b/src/com/android/contacts/editor/LabeledEditorView.java
index df4c5f9..d393b3b 100644
--- a/src/com/android/contacts/editor/LabeledEditorView.java
+++ b/src/com/android/contacts/editor/LabeledEditorView.java
@@ -51,6 +51,7 @@
import com.android.contacts.model.ValuesDelta;
import com.android.contacts.model.account.AccountType.EditType;
import com.android.contacts.model.dataitem.DataKind;
+import com.android.contacts.model.account.SimAccountType;
import com.android.contacts.util.DialogManager;
import com.android.contacts.util.DialogManager.DialogShowingView;
@@ -234,6 +235,14 @@
}
}
+ private boolean isSimAccount() {
+ if (mState != null && mState.getAccountType() != null
+ && mState.getAccountType().equals(SimAccountType.ACCOUNT_TYPE)) {
+ return true;
+ }
+ return false;
+ }
+
public void setDeleteButtonVisible(boolean visible) {
if (mIsDeletable) {
mDeleteContainer.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
@@ -417,7 +426,7 @@
setVisibility(View.VISIBLE);
// Display label selector if multiple types available
- final boolean hasTypes = RawContactModifier.hasEditTypes(kind);
+ final boolean hasTypes = RawContactModifier.hasEditTypes(kind) && !isSimAccount();
setupLabelButton(hasTypes);
mLabel.setEnabled(!readOnly && isEnabled());
if (mKind.titleRes > 0) {
diff --git a/src/com/android/contacts/editor/PickRawContactDialogFragment.java b/src/com/android/contacts/editor/PickRawContactDialogFragment.java
index 5a9c9fd..fe5d7d2 100644
--- a/src/com/android/contacts/editor/PickRawContactDialogFragment.java
+++ b/src/com/android/contacts/editor/PickRawContactDialogFragment.java
@@ -3,6 +3,7 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
+import android.accounts.Account;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -129,12 +130,15 @@
holder.accountName.setText(accountDisplayLabel);
holder.accountIcon.setImageDrawable(account.getDisplayIcon(mContext));
+ Account accounts = null;
+ if(rawContact.accountType != null)
+ accounts = new Account(rawContact.accountName, rawContact.accountType);
final ContactPhotoManager.DefaultImageRequest
request = new ContactPhotoManager.DefaultImageRequest(
displayName, String.valueOf(rawContact.id), /* isCircular = */ true);
ContactPhotoManager.getInstance(mContext).loadThumbnail(holder.photo,
- rawContact.photoId,
+ rawContact.photoId, accounts,
/* darkTheme = */ false,
/* isCircular = */ true,
request);
diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java
index 19da5bc..22593b3 100644
--- a/src/com/android/contacts/editor/RawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -61,6 +61,7 @@
import com.android.contacts.model.account.AccountInfo;
import com.android.contacts.model.account.AccountType;
import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.model.account.SimAccountType;
import com.android.contacts.model.dataitem.CustomDataItem;
import com.android.contacts.model.dataitem.DataKind;
import com.android.contacts.util.AccountsListAdapter;
@@ -116,6 +117,8 @@
* Invoked after editors have been bound for the contact.
*/
public void onEditorsBound();
+
+ public void onClearPhotoCache();
}
/**
* Sorts kinds roughly the same as quick contacts; we diverge in the following ways:
@@ -824,6 +827,10 @@
UiClosables.closeQuietly(popup);
final AccountWithDataSet newAccount = adapter.getItem(position);
if (mListener != null && !mPrimaryAccount.equals(newAccount)) {
+ if (newAccount !=null && newAccount.type != null
+ && SimAccountType.ACCOUNT_TYPE.equals(newAccount.type)) {
+ mListener.onClearPhotoCache();
+ }
mIsExpanded = false;
mListener.onRebindEditorsForNewContact(
rawContactDelta,
diff --git a/src/com/android/contacts/interactions/ExportDialogFragment.java b/src/com/android/contacts/interactions/ExportDialogFragment.java
index c7ce072..6d5888b 100644
--- a/src/com/android/contacts/interactions/ExportDialogFragment.java
+++ b/src/com/android/contacts/interactions/ExportDialogFragment.java
@@ -39,11 +39,17 @@
import android.widget.Toast;
import com.android.contacts.R;
+import com.android.contacts.SimContactsConstants;
+import com.android.contacts.list.AccountFilterActivity;
+import com.android.contacts.list.ContactListFilter;
import com.android.contacts.util.ImplicitIntentsUtil;
import com.android.contacts.vcard.ExportVCardActivity;
+import com.android.contacts.database.SimContactDao;
+import com.android.contacts.model.SimCard;
import com.android.contacts.vcard.ShareVCardActivity;
import com.android.contacts.vcard.VCardCommonArguments;
+import java.util.List;
/**
* An dialog invoked to import/export contacts.
*/
@@ -61,7 +67,7 @@
};
private SubscriptionManager mSubscriptionManager;
-
+ private SimContactDao mSimDao;
/** Preferred way to show this dialog */
public static void show(FragmentManager fragmentManager, Class callingActivity,
int exportMode) {
@@ -91,7 +97,7 @@
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final String callingActivity = getArguments().getString(
VCardCommonArguments.ARG_CALLING_ACTIVITY);
-
+ mSimDao = SimContactDao.create(getContext());
// Adapter that shows a list of string resources
final ArrayAdapter<AdapterEntry> adapter = new ArrayAdapter<AdapterEntry>(getActivity(),
R.layout.select_dialog_item) {
@@ -126,6 +132,8 @@
}
}
+ addItems(adapter);
+
final DialogInterface.OnClickListener clickListener =
new DialogInterface.OnClickListener() {
@Override
@@ -150,6 +158,14 @@
callingActivity);
getActivity().startActivity(exportIntent);
}
+ } else if (resId == R.string.export_to_sim) {
+ dismissDialog = true;
+ int sub = adapter.getItem(which).mSim.getSubscriptionId();
+ Intent pickContactIntent = new Intent(
+ SimContactsConstants.ACTION_MULTI_PICK_CONTACT,
+ Contacts.CONTENT_URI);
+ pickContactIntent.putExtra("exportSub", sub);
+ getActivity().startActivity(pickContactIntent);
} else {
dismissDialog = true;
Log.e(TAG, "Unexpected resource: "
@@ -214,21 +230,46 @@
}
}
+ private void addItems(ArrayAdapter<AdapterEntry> adapter) {
+ final List<SimCard> sims = mSimDao.getSimCards();
+
+ if (sims.size() == 1) {
+ adapter.add(new AdapterEntry(getString(R.string.import_from_sim),
+ R.string.export_to_sim, sims.get(0)));
+ } else {
+ for (int i = 0; i < sims.size(); i++) {
+ final SimCard sim = sims.get(i);
+ adapter.add(new AdapterEntry(getSimDescription(sim, i),
+ R.string.export_to_sim, sim));
+ }
+ }
+ }
+
+ // use same display res as import from sim
+ private CharSequence getSimDescription(SimCard sim, int index) {
+ final CharSequence name = sim.getDisplayName();
+ if (name != null) {
+ return getString(R.string.import_from_sim_summary_fmt, name);
+ } else {
+ return getString(R.string.import_from_sim_summary_fmt, String.valueOf(index));
+ }
+ }
+
private static class AdapterEntry {
public final CharSequence mLabel;
public final int mChoiceResourceId;
- public final int mSubscriptionId;
+ public final SimCard mSim;
- public AdapterEntry(CharSequence label, int resId, int subId) {
+ public AdapterEntry(CharSequence label, int resId, SimCard sim) {
mLabel = label;
mChoiceResourceId = resId;
- mSubscriptionId = subId;
+ mSim = sim;
}
public AdapterEntry(String label, int resId) {
// Store a nonsense value for mSubscriptionId. If this constructor is used,
// the mSubscriptionId value should not be read later.
- this(label, resId, /* subId = */ -1);
+ this(label, resId, /* subId = */ null);
}
}
}
diff --git a/src/com/android/contacts/interactions/ImportDialogFragment.java b/src/com/android/contacts/interactions/ImportDialogFragment.java
index 41b1c71..6335cfe 100644
--- a/src/com/android/contacts/interactions/ImportDialogFragment.java
+++ b/src/com/android/contacts/interactions/ImportDialogFragment.java
@@ -35,6 +35,7 @@
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
+import android.widget.Toast;
import com.android.contacts.R;
import com.android.contacts.activities.SimImportActivity;
@@ -113,7 +114,7 @@
// Start loading the accounts. This is done in onResume in case they were refreshed.
mAccountsFuture = AccountTypeManager.getInstance(getActivity()).filterAccountsAsync(
- AccountTypeManager.writableFilter());
+ AccountTypeManager.AccountFilter.CONTACTS_WRITABLE_WITHOUT_SIM);
}
@Override
@@ -259,6 +260,11 @@
* Handle "import from SD".
*/
private void handleImportRequest(int resId, int subscriptionId) {
+ //if the accounts is not initial complete, give a toast here.
+ if (mAccountsFuture == null) {
+ Toast.makeText(getActivity(), R.string.vcard_import_failed, Toast.LENGTH_SHORT).show();
+ return;
+ }
// Get the accounts. Because this only happens after a user action this should pretty
// much never block since it will usually be at least several seconds before the user
// interacts with the view
@@ -277,7 +283,7 @@
args.putInt(KEY_SUBSCRIPTION_ID, subscriptionId);
SelectAccountDialogFragment.show(
getFragmentManager(), R.string.dialog_new_contact_account,
- AccountTypeManager.AccountFilter.CONTACTS_WRITABLE, args);
+ AccountTypeManager.AccountFilter.CONTACTS_WRITABLE_WITHOUT_SIM, args);
} else {
AccountSelectionUtil.doImport(getActivity(), resId,
(size == 1 ? accountList.get(0) : null),
diff --git a/src/com/android/contacts/lettertiles/LetterTileDrawable.java b/src/com/android/contacts/lettertiles/LetterTileDrawable.java
index b80fd4f..a8631f7 100644
--- a/src/com/android/contacts/lettertiles/LetterTileDrawable.java
+++ b/src/com/android/contacts/lettertiles/LetterTileDrawable.java
@@ -16,6 +16,8 @@
package com.android.contacts.lettertiles;
+import android.accounts.Account;
+import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -29,7 +31,11 @@
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
+import android.telephony.TelephonyManager;
+import com.android.contacts.ContactUtils;
+import com.android.contacts.SimContactsConstants;
+import com.android.contacts.model.account.SimAccountType;
import com.android.contacts.R;
import com.google.common.base.Preconditions;
@@ -52,6 +58,9 @@
private static Bitmap DEFAULT_PERSON_AVATAR;
private static Bitmap DEFAULT_BUSINESS_AVATAR;
private static Bitmap DEFAULT_VOICEMAIL_AVATAR;
+ private static Bitmap DEFAULT_SIM_PERSON_AVATAR;
+ private static Bitmap[] DEFAULT_CUSTOMIZE_SIM_PERSON_AVATAR =
+ new Bitmap[ContactUtils.IC_SIM_PICTURE.length];
/** Reusable components to avoid new allocations */
private static final Paint sPaint = new Paint();
@@ -74,8 +83,13 @@
private int mColor;
private Character mLetter = null;
+ private Account mAccount;
public LetterTileDrawable(final Resources res) {
+ this(res, null);
+ }
+
+ public LetterTileDrawable(final Resources res, final Account account) {
if (sColors == null) {
sColors = res.obtainTypedArray(R.array.letter_tile_colors);
sDefaultColor = res.getColor(R.color.letter_tile_default_color);
@@ -87,6 +101,12 @@
R.drawable.ic_business_white_120dp);
DEFAULT_VOICEMAIL_AVATAR = BitmapFactory.decodeResource(res,
R.drawable.ic_voicemail_avatar);
+ DEFAULT_SIM_PERSON_AVATAR = BitmapFactory.decodeResource(res,
+ R.drawable.ic_contact_picture_sim);
+ for (int i = 0; i < ContactUtils.IC_SIM_PICTURE.length; i++) {
+ DEFAULT_CUSTOMIZE_SIM_PERSON_AVATAR[i] = BitmapFactory
+ .decodeResource(res, ContactUtils.IC_SIM_PICTURE[i]);
+ }
sPaint.setTypeface(Typeface.create(
res.getString(R.string.letter_tile_letter_font_family), Typeface.NORMAL));
sPaint.setTextAlign(Align.CENTER);
@@ -96,6 +116,7 @@
mPaint.setFilterBitmap(true);
mPaint.setDither(true);
mColor = sDefaultColor;
+ mAccount = account;
}
@Override
@@ -151,7 +172,8 @@
// Draw letter/digit only if the first character is an english letter or there's a override
- if (mLetter != null) {
+ if (mLetter != null && (mAccount == null || (mAccount != null && !mAccount.type
+ .equals(SimAccountType.ACCOUNT_TYPE)))) {
// Draw letter or digit.
sFirstChar[0] = mLetter;
@@ -169,7 +191,7 @@
sPaint);
} else {
// Draw the default image if there is no letter/digit to be drawn
- final Bitmap bitmap = getBitmapForContactType(mContactType);
+ final Bitmap bitmap = getBitmapForContactType(mContactType, mAccount);
drawBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight(),
canvas);
}
@@ -193,7 +215,17 @@
return sColors.getColor(color, sDefaultColor);
}
- private static Bitmap getBitmapForContactType(int contactType) {
+ private static Bitmap getBitmapForContactType(int contactType, Account account) {
+ if (account != null && SimAccountType.ACCOUNT_TYPE.equals(account.type)) {
+ if (account.name.equals(SimContactsConstants.SIM_NAME))
+ return DEFAULT_SIM_PERSON_AVATAR;
+ final int slot = ContactUtils.getSubscription(
+ SimAccountType.ACCOUNT_TYPE, account.name);
+ if (slot < 0 || slot > ContactUtils.IC_SIM_PICTURE.length) {
+ return DEFAULT_PERSON_AVATAR;
+ }
+ return DEFAULT_CUSTOMIZE_SIM_PERSON_AVATAR[slot];
+ }
switch (contactType) {
case TYPE_PERSON:
return DEFAULT_PERSON_AVATAR;
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index b508dda..41d617a 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -15,6 +15,7 @@
*/
package com.android.contacts.list;
+import android.accounts.Account;
import android.content.Context;
import android.content.CursorLoader;
import android.content.res.Resources;
@@ -705,12 +706,18 @@
*/
protected void bindQuickContact(final ContactListItemView view, int partitionIndex,
Cursor cursor, int photoIdColumn, int photoUriColumn, int contactIdColumn,
- int lookUpKeyColumn, int displayNameColumn) {
+ int lookUpKeyColumn, int displayNameColumn, int accountTypeColume,
+ int accountNameColume) {
long photoId = 0;
if (!cursor.isNull(photoIdColumn)) {
photoId = cursor.getLong(photoIdColumn);
}
-
+ Account account = null;
+ if (!cursor.isNull(accountTypeColume) && !cursor.isNull(accountNameColume)) {
+ final String accountType = cursor.getString(accountTypeColume);
+ final String accountName = cursor.getString(accountNameColume);
+ account = new Account(accountName, accountType);
+ }
QuickContactBadge quickContact = view.getQuickContact();
quickContact.assignContactUri(
getContactUri(partitionIndex, cursor, contactIdColumn, lookUpKeyColumn));
@@ -722,8 +729,8 @@
}
if (photoId != 0 || photoUriColumn == -1) {
- getPhotoLoader().loadThumbnail(quickContact, photoId, mDarkTheme, mCircularPhotos,
- null);
+ getPhotoLoader().loadThumbnail(quickContact, photoId, account, mDarkTheme,
+ mCircularPhotos, null);
} else {
final String photoUriString = cursor.getString(photoUriColumn);
final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
@@ -732,8 +739,8 @@
request = getDefaultImageRequestFromCursor(cursor, displayNameColumn,
lookUpKeyColumn);
}
- getPhotoLoader().loadPhoto(quickContact, photoUri, -1, mDarkTheme, mCircularPhotos,
- request);
+ getPhotoLoader().loadPhoto(quickContact, photoUri, account, -1,
+ mDarkTheme, mCircularPhotos, request);
}
}
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index cb63f53..1ecfe34 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -15,6 +15,7 @@
*/
package com.android.contacts.list;
+import android.accounts.Account;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
@@ -23,6 +24,7 @@
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Directory;
import android.provider.ContactsContract.SearchSnippets;
+import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
import android.view.ViewGroup;
import android.widget.ListView;
@@ -50,7 +52,9 @@
Contacts.PHOTO_THUMBNAIL_URI, // 5
Contacts.LOOKUP_KEY, // 6
Contacts.PHONETIC_NAME, // 7
- Contacts.STARRED, // 9
+ Contacts.STARRED, // 8
+ RawContacts.ACCOUNT_TYPE, // 9
+ RawContacts.ACCOUNT_NAME, // 10
};
private static final String[] CONTACT_PROJECTION_ALTERNATIVE = new String[] {
@@ -63,6 +67,8 @@
Contacts.LOOKUP_KEY, // 6
Contacts.PHONETIC_NAME, // 7
Contacts.STARRED, // 8
+ RawContacts.ACCOUNT_TYPE, // 9
+ RawContacts.ACCOUNT_NAME, // 10
};
private static final String[] FILTER_PROJECTION_PRIMARY = new String[] {
@@ -75,7 +81,9 @@
Contacts.LOOKUP_KEY, // 6
Contacts.PHONETIC_NAME, // 7
Contacts.STARRED, // 8
- SearchSnippets.SNIPPET, // 9
+ RawContacts.ACCOUNT_TYPE, // 9
+ RawContacts.ACCOUNT_NAME, // 10
+ SearchSnippets.SNIPPET, // 11
};
private static final String[] FILTER_PROJECTION_ALTERNATIVE = new String[] {
@@ -88,7 +96,9 @@
Contacts.LOOKUP_KEY, // 6
Contacts.PHONETIC_NAME, // 7
Contacts.STARRED, // 8
- SearchSnippets.SNIPPET, // 9
+ RawContacts.ACCOUNT_TYPE, // 9
+ RawContacts.ACCOUNT_NAME, // 10
+ SearchSnippets.SNIPPET, // 11
};
public static final int CONTACT_ID = 0;
@@ -100,7 +110,9 @@
public static final int CONTACT_LOOKUP_KEY = 6;
public static final int CONTACT_PHONETIC_NAME = 7;
public static final int CONTACT_STARRED = 8;
- public static final int CONTACT_SNIPPET = 9;
+ public static final int CONTACT_ACCOUNT_TYPE = 9;
+ public static final int CONTACT_ACCOUNT_NAME = 10;
+ public static final int CONTACT_SNIPPET = 11;
}
private CharSequence mUnknownNameText;
@@ -252,9 +264,15 @@
if (!cursor.isNull(ContactQuery.CONTACT_PHOTO_ID)) {
photoId = cursor.getLong(ContactQuery.CONTACT_PHOTO_ID);
}
-
+ Account account = null;
+ if (!cursor.isNull(ContactQuery.CONTACT_ACCOUNT_TYPE)
+ && !cursor.isNull(ContactQuery.CONTACT_ACCOUNT_NAME)) {
+ final String accountType = cursor.getString(ContactQuery.CONTACT_ACCOUNT_TYPE);
+ final String accountName = cursor.getString(ContactQuery.CONTACT_ACCOUNT_NAME);
+ account = new Account(accountName, accountType);
+ }
if (photoId != 0) {
- getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false,
+ getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, account, false,
getCircularPhotos(), null);
} else {
final String photoUriString = cursor.getString(ContactQuery.CONTACT_PHOTO_URI);
@@ -265,7 +283,7 @@
ContactQuery.CONTACT_DISPLAY_NAME,
ContactQuery.CONTACT_LOOKUP_KEY);
}
- getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false,
+ getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, account, false,
getCircularPhotos(), request);
}
}
diff --git a/src/com/android/contacts/list/ContactListFilter.java b/src/com/android/contacts/list/ContactListFilter.java
index 32e4b9c..5f93a0a 100644
--- a/src/com/android/contacts/list/ContactListFilter.java
+++ b/src/com/android/contacts/list/ContactListFilter.java
@@ -45,6 +45,7 @@
public static final int FILTER_TYPE_SINGLE_CONTACT = -6;
public static final int FILTER_TYPE_GROUP_MEMBERS = -7;
public static final int FILTER_TYPE_DEVICE_CONTACTS = -8;
+ public static final int FILTER_TYPE_ALL_WITHOUT_SIM = -9;
public static final int FILTER_TYPE_ACCOUNT = 0;
diff --git a/src/com/android/contacts/list/ContactPickerFragment.java b/src/com/android/contacts/list/ContactPickerFragment.java
index 504828f..a1a460a 100644
--- a/src/com/android/contacts/list/ContactPickerFragment.java
+++ b/src/com/android/contacts/list/ContactPickerFragment.java
@@ -136,7 +136,7 @@
HeaderEntryContactListAdapter adapter
= new HeaderEntryContactListAdapter(getActivity());
adapter.setFilter(ContactListFilter.createFilterWithType(
- ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS));
+ ContactListFilter.FILTER_TYPE_ALL_WITHOUT_SIM));
adapter.setSectionHeaderDisplayEnabled(true);
adapter.setDisplayPhotos(true);
adapter.setQuickContactEnabled(false);
diff --git a/src/com/android/contacts/list/ContactsFragment.java b/src/com/android/contacts/list/ContactsFragment.java
new file mode 100644
index 0000000..778cd6d
--- /dev/null
+++ b/src/com/android/contacts/list/ContactsFragment.java
@@ -0,0 +1,765 @@
+/*
+ * Copyright (c) 2013-2018, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.contacts.list;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.ListFragment;
+import android.content.Context;
+import android.content.Intent;
+import android.content.AsyncQueryHandler;
+import android.database.Cursor;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.RawContacts;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.CheckBox;
+import android.widget.ListView;
+import android.widget.AbsListView;
+import android.widget.SectionIndexer;
+import android.widget.TextView;
+import android.widget.ImageView;
+
+import com.android.contacts.activities.MultiPickContactsActivity;
+import com.android.contacts.ContactPhotoManager;
+import com.android.contacts.ContactPhotoManager.DefaultImageRequest;
+import com.android.contacts.format.TextHighlighter;
+import com.android.contacts.list.AccountFilterActivity;
+import com.android.contacts.list.ContactsSectionIndexer;
+import com.android.contacts.list.ContactListFilter;
+import com.android.contacts.SimContactsConstants;
+import com.android.contacts.util.UriUtils;
+import com.android.contacts.list.ContactsPickMode;
+import com.android.contacts.list.OnCheckListActionListener;
+import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.R;
+
+import java.util.ArrayList;
+
+public class ContactsFragment extends ListFragment {
+ private final static String TAG = "ContactsFragment";
+ private final static boolean DEBUG = true;
+ private static final String SORT_ORDER = " desc";
+
+ static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+ Contacts._ID, // 0
+ Contacts.NAME_RAW_CONTACT_ID, // 1
+ Contacts.LOOKUP_KEY, // 2
+ Contacts.DISPLAY_NAME_PRIMARY, // 3
+ Contacts.PHOTO_ID, // 4
+ Contacts.PHOTO_THUMBNAIL_URI, // 5
+ RawContacts.ACCOUNT_TYPE, // 6
+ RawContacts.ACCOUNT_NAME, // 7
+ };
+ static final String CONTACTS_SELECTION = Contacts.IN_VISIBLE_GROUP + "=1";
+ static final String LOCAL_SELECTION = RawContacts.ACCOUNT_TYPE + " IS NULL ";
+
+ private static final String[] DATA_PROJECTION = new String[] {
+ Data._ID, // 0
+ Data.CONTACT_ID, // 1
+ Contacts.LOOKUP_KEY, // 2
+ Data.DISPLAY_NAME, // 3
+ Contacts.PHOTO_ID,// 4
+ Contacts.PHOTO_THUMBNAIL_URI, // 5
+ RawContacts.ACCOUNT_TYPE, // 6
+ RawContacts.ACCOUNT_NAME, // 7
+ Data.DATA1, // 8 Phone.NUMBER, Email.address
+ Data.DATA2, // 9 phone.type
+ Data.DATA3, // 10 Phone.LABEL
+ Data.MIMETYPE, //11
+ };
+
+ // contacts column
+ private static final int SUMMARY_ID_COLUMN_INDEX = 0;
+ private static final int SUMMARY_COLUMN_CONTACT_ID = 1;
+ private static final int SUMMARY_LOOKUP_KEY_COLUMN_INDEX = 2;
+ private static final int SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 3;
+ private static final int SUMMARY_PHOTO_ID_COLUMN_INDEX = 4;
+ private static final int SUMMARY_CONTACT_COLUMN_PHOTO_URI = 5;
+ private static final int SUMMARY_ACCOUNT_TYPE = 6;
+ private static final int SUMMARY_ACCOUNT_NAME = 7;
+
+ private static final int DATA_DATA1_COLUMN = 8;
+ private static final int DATA_DATA2_COLUMN = 9; //phone.type
+ private static final int DATA_DATA3_COLUMN = 10; //Phone.LABEL
+ private static final int DATA_MIMETYPE_COLUMN = 11;
+
+ private static final int QUERY_TOKEN = 43;
+
+ private int subscription;
+ private QueryHandler mQueryHandler;
+ private Bundle mChoiceSet;
+ private TextView mSelectAllLabel;
+ private Intent mIntent;
+ private Context mContext;
+ private ContactsPickMode mPickMode;
+ private int mMode;
+ private OnCheckListActionListener mCheckListListener;
+ private ContactItemListAdapter mContactListAdapter;
+ private String query;
+ private View mRootView;
+ private SectionIndexer mIndexer;
+ private View mHeaderView;
+ private ContactListFilter mFilter;
+
+ /**
+ * An item view is displayed differently depending on whether it is placed at the beginning,
+ * middle or end of a section. It also needs to know the section header when it is at the
+ * beginning of a section. This object captures all this configuration.
+ */
+ public static final class Placement {
+ private int position = ListView.INVALID_POSITION;
+ public boolean firstInSection;
+ public boolean lastInSection;
+ public String sectionHeader;
+
+ public void invalidate() {
+ position = ListView.INVALID_POSITION;
+ }
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mPickMode = ContactsPickMode.getInstance();
+ mMode = mPickMode.getMode();
+ mFilter = (ContactListFilter) mPickMode.getIntent().getParcelableExtra(
+ AccountFilterActivity.EXTRA_CONTACT_LIST_FILTER);
+ if (mFilter == null)
+ mFilter = ContactListFilter
+ .restoreDefaultPreferences(PreferenceManager
+ .getDefaultSharedPreferences(mContext));
+ if (mContactListAdapter == null) {
+ mContactListAdapter = new ContactItemListAdapter(mContext);
+ }
+
+ mHeaderView = new View(mContext);
+ AbsListView.LayoutParams layoutParams = new AbsListView.LayoutParams(
+ AbsListView.LayoutParams.MATCH_PARENT,
+ (int)(mContext.getResources().getDimension(R.dimen.header_listview_height)));
+ mHeaderView.setLayoutParams(layoutParams);
+ getListView().addHeaderView(mHeaderView, null, false);
+ setListAdapter(mContactListAdapter);
+ mQueryHandler = new QueryHandler(mContext);
+ startQuery();
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mContext = (MultiPickContactsActivity) activity;
+ }
+
+ @Override
+ public void onResume() {
+ // ContactsPickMode is singleton, its mode may be changed by other mode.
+ // need to reset
+ mPickMode.setMode(mMode);
+ super.onResume();
+ }
+
+ @Override
+ public void onStop() {
+ mMode = mPickMode.getMode();
+ super.onStop();
+ }
+
+ public void setCheckListListener(OnCheckListActionListener checkListListener) {
+ mCheckListListener = checkListListener;
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ mCheckListListener.onHideSoftKeyboard();
+ CheckBox checkBox = (CheckBox) v.findViewById(R.id.pick_contact_check);
+ boolean isChecked = !checkBox.isChecked();
+ checkBox.setChecked(isChecked);
+ ContactItemCache cache = (ContactItemCache) v.getTag();
+ String key = String.valueOf(cache.id);
+ if (!mCheckListListener.onContainsKey(key)) {
+ String[] value = null;
+ if (mPickMode.isPickContact()) {
+ value = new String[] {
+ cache.lookupKey, key,
+ String.valueOf(cache.nameRawContactId),
+ cache.photoUri == null ? null : String
+ .valueOf(cache.photoUri), cache.name,
+ cache.accountType, cache.accountName};
+ } else if (mPickMode.isPickPhone()) {
+ value = new String[] { cache.name, cache.number, cache.type,
+ cache.label, cache.contact_id };
+ } else if (mPickMode.isPickEmail()) {
+ value = new String[] {cache.name, cache.email};
+ }
+ mCheckListListener.putValue(key, value);
+ } else {
+ mCheckListListener.onRemove(key);
+ }
+ mCheckListListener.exitSearch();
+ mCheckListListener.onUpdateActionBar();
+ mContactListAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mContext = null;
+ }
+
+ @Override
+ public void onDestroy() {
+ mQueryHandler.removeCallbacksAndMessages(QUERY_TOKEN);
+
+ if (mContactListAdapter.getCursor() != null) {
+ mContactListAdapter.getCursor().close();
+ }
+ super.onDestroy();
+ }
+
+ private Uri getUriToQuery() {
+ Uri uri;
+ switch (mPickMode.getMode()) {
+ case ContactsPickMode.MODE_DEFAULT_CONTACT:
+ case ContactsPickMode.MODE_SEARCH_CONTACT:
+ uri = Contacts.CONTENT_URI;
+ break;
+ case ContactsPickMode.MODE_DEFAULT_EMAIL:
+ case ContactsPickMode.MODE_SEARCH_EMAIL:
+ uri = Email.CONTENT_URI;
+ break;
+ case ContactsPickMode.MODE_DEFAULT_PHONE:
+ case ContactsPickMode.MODE_SEARCH_PHONE:
+ uri = Phone.CONTENT_URI;
+ uri = uri.buildUpon().appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+ String.valueOf(Directory.DEFAULT))
+ .appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true")
+ .build();
+ break;
+ default:
+ uri = Contacts.CONTENT_URI;
+ }
+ return uri.buildUpon().appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true")
+ .build();
+ }
+
+ private Uri getFilterUri() {
+ switch (mPickMode.getMode()) {
+ case ContactsPickMode.MODE_SEARCH_CONTACT:
+ return Contacts.CONTENT_FILTER_URI;
+ case ContactsPickMode.MODE_SEARCH_PHONE:
+ return Phone.CONTENT_FILTER_URI;
+ case ContactsPickMode.MODE_SEARCH_EMAIL:
+ return Email.CONTENT_FILTER_URI;
+ default:
+ log("getFilterUri: Incorrect mode: " + mPickMode.getMode());
+ }
+ return Contacts.CONTENT_FILTER_URI;
+ }
+
+ public String[] getProjectionForQuery() {
+ switch (mPickMode.getMode()) {
+ case ContactsPickMode.MODE_DEFAULT_CONTACT:
+ case ContactsPickMode.MODE_SEARCH_CONTACT:
+ return CONTACTS_SUMMARY_PROJECTION;
+ case ContactsPickMode.MODE_DEFAULT_PHONE:
+ case ContactsPickMode.MODE_SEARCH_PHONE:
+ case ContactsPickMode.MODE_DEFAULT_EMAIL:
+ case ContactsPickMode.MODE_SEARCH_EMAIL:
+ return DATA_PROJECTION;
+ default:
+ log("getProjectionForQuery: Incorrect mode: " + mPickMode.getMode());
+ }
+ return CONTACTS_SUMMARY_PROJECTION;
+ }
+
+ private String getSortOrder(String[] projection) {
+ return RawContacts.SORT_KEY_PRIMARY;
+ }
+
+ private String getSelectionForAccount() {
+ @SuppressWarnings("deprecation")
+ StringBuilder selection = new StringBuilder();
+ if(mFilter == null)
+ return null;
+ switch (mFilter.filterType) {
+ case ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS:
+ return null;
+ case ContactListFilter.FILTER_TYPE_CUSTOM:
+ return CONTACTS_SELECTION;
+ case ContactListFilter.FILTER_TYPE_ACCOUNT:
+ if (mPickMode.isSearchMode())
+ selection.append(RawContacts.ACCOUNT_TYPE).append("='")
+ .append(mFilter.accountType).append("' AND ")
+ .append(RawContacts.ACCOUNT_NAME).append("='")
+ .append(mFilter.accountName).append("'");
+ return selection.toString();
+ case ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS:
+ return LOCAL_SELECTION;
+ }
+ return null;
+ }
+
+ public void startQuery() {
+ Uri uri = getUriToQuery();
+ if (mFilter != null
+ && mFilter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) {
+ // We should exclude the invisiable contacts.
+ uri = uri.buildUpon()
+ .appendQueryParameter(RawContacts.ACCOUNT_NAME,
+ mFilter.accountName)
+ .appendQueryParameter(RawContacts.ACCOUNT_TYPE,
+ mFilter.accountType)
+ .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+ ContactsContract.Directory.DEFAULT + "").build();
+ }
+
+ String[] projection = getProjectionForQuery();
+ String selection = getSelectionForAccount();
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, selection, null,
+ getSortOrder(projection));
+ }
+
+ public void doFilter(String s) {
+ query = s;
+ if (TextUtils.isEmpty(s)) {
+ mContactListAdapter.changeCursor(null);
+ return;
+ }
+ Uri uri = Uri.withAppendedPath(getFilterUri(), Uri.encode(query));
+ String[] projection = getProjectionForQuery();
+ String selection = getSelectionForAccount();
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, selection, null,
+ getSortOrder(projection));
+ }
+
+ private class QueryHandler extends AsyncQueryHandler {
+ public QueryHandler(Context context) {
+ super(context.getContentResolver());
+ }
+
+ @Override
+ protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ if (mHeaderView != null && mPickMode.isSearchMode()) {
+ getListView().removeHeaderView(mHeaderView);
+ }
+ mContactListAdapter.changeCursor(cursor);
+ }
+ }
+
+ private class ContactItemCache {
+ long id;
+ String name;
+ String number;
+ String lookupKey;
+ String type;
+ String label;
+ String contact_id;
+ String email;
+ String accountType;
+ String accountName;
+ long nameRawContactId;
+ Uri photoUri;
+ Uri contactUri;
+ long photoId;
+ }
+
+ public class ContactItemListAdapter extends CursorAdapter
+ implements SectionIndexer {
+ protected LayoutInflater mInflater;
+ private ContactPhotoManager mContactPhotoManager;
+ private final TextHighlighter mTextHighlighter;
+ private Placement mPlacement = new Placement();
+
+ public ContactItemListAdapter(Context context) {
+ super(context, null, false);
+ mInflater = (LayoutInflater) mContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mContactPhotoManager = ContactPhotoManager.getInstance(mContext);
+ mTextHighlighter = new TextHighlighter(Typeface.BOLD);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ ContactItemCache cache = (ContactItemCache) view.getTag();
+ if (mPickMode.isPickContact()) {
+ cache.id = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
+ cache.lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
+ cache.name = cursor.getString(SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX);
+ cache.nameRawContactId = cursor.getLong(SUMMARY_COLUMN_CONTACT_ID);
+ cache.accountType = cursor.getString(SUMMARY_ACCOUNT_TYPE);
+ cache.accountName = cursor.getString(SUMMARY_ACCOUNT_NAME);
+ ((TextView) view.findViewById(R.id.pick_contact_name))
+ .setText(cache.name == null ? "" : cache.name);
+ view.findViewById(R.id.pick_contact_number).setVisibility(View.GONE);
+ setPhotoView(view, cursor, cache);
+ setHeaderAndHighLightIfNeed(view, cache, cursor);
+ } else if (mPickMode.isPickPhone()) {
+ cache.id = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
+ cache.name = cursor.getString(SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX);
+ cache.number = cursor.getString(DATA_DATA1_COLUMN);
+ cache.label = cursor.getString(DATA_DATA3_COLUMN);
+ cache.type = String.valueOf(cursor.getInt(DATA_DATA2_COLUMN));
+ ((TextView) view.findViewById(R.id.pick_contact_number)).setText(cache.number);
+ setPhotoView(view, cursor, cache);
+ setItemView(view, cursor, cache);
+ setLabel(view, cursor);
+ setHeaderAndHighLightIfNeed(view, cache, cursor);
+ } else if (mPickMode.isPickEmail()) {
+ cache.id = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
+ cache.name = cursor.getString(SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX);
+ cache.email = cursor.getString(DATA_DATA1_COLUMN);
+ ((TextView) view.findViewById(R.id.pick_contact_number)).setText(cache.email);
+ setPhotoView(view, cursor, cache);
+ setItemView(view, cursor, cache);
+ setLabel(view, cursor);
+ setHeaderAndHighLightIfNeed(view, cache, cursor);
+ }
+
+ CheckBox checkBox = (CheckBox) view.findViewById(R.id.pick_contact_check);
+ if (mCheckListListener.onContainsKey(String.valueOf(cache.id))) {
+ checkBox.setChecked(true);
+ } else {
+ checkBox.setChecked(false);
+ }
+ }
+
+ private void setHeaderAndHighLightIfNeed(View view, ContactItemCache cache,
+ Cursor cursor) {
+ if (mPickMode.isSearchMode()) {
+ hideSectionHeader(view);
+ if (!TextUtils.isEmpty(query)) {
+ setFilterHighLight(view, cache);
+ }
+ } else {
+ bindSectionHeader(view, cursor.getPosition());
+ }
+ }
+
+ private void setFilterHighLight(View view, ContactItemCache cache) {
+ TextView nameView = (TextView) view.findViewById(R.id.pick_contact_name);
+ CharSequence nameText = cache.name;
+ nameText = mTextHighlighter.applyPrefixHighlight(nameText, query.toUpperCase());
+ nameView.setText(nameText);
+ TextView numberView = (TextView) view.findViewById(R.id.pick_contact_number);
+ if (mPickMode.isPickEmail()) {
+ CharSequence emailText = cache.email;
+ emailText = mTextHighlighter.applyPrefixHighlight(emailText, query.toUpperCase());
+ numberView.setText(emailText);
+ } else {
+ CharSequence numberText = cache.number;
+ numberText = mTextHighlighter.applyPrefixHighlight(numberText, query.toUpperCase());
+ numberView.setText(numberText);
+ }
+ }
+
+ private void setLabel(View view, Cursor cursor) {
+ TextView labelView = (TextView) view.findViewById(R.id.label);
+ CharSequence label = null;
+ if (!cursor.isNull(DATA_DATA2_COLUMN)) {
+ final int type = cursor.getInt(DATA_DATA2_COLUMN);
+ final String customLabel = cursor.getString(DATA_DATA3_COLUMN);
+ label = Phone.getTypeLabel(mContext.getResources(), type, customLabel);
+ }
+ labelView.setText(label);
+ }
+
+ private void setItemView(View view, Cursor cursor, ContactItemCache cache) {
+ ImageView photoView = (ImageView) view
+ .findViewById(R.id.pick_contact_photo);
+ boolean isFirstEntry = true;
+ if (!cursor.isNull(SUMMARY_COLUMN_CONTACT_ID)) {
+ long currentContactId = cursor.getLong(SUMMARY_COLUMN_CONTACT_ID);
+ int position = cursor.getPosition();
+ if (!cursor.isFirst() && cursor.moveToPrevious()) {
+ if (!cursor.isNull(SUMMARY_COLUMN_CONTACT_ID)) {
+ final long previousContactId = cursor.getLong(SUMMARY_COLUMN_CONTACT_ID);
+ if (currentContactId == previousContactId) {
+ isFirstEntry = false;
+ }
+ }
+ }
+ cursor.moveToPosition(position);
+ }
+
+ if (isFirstEntry) {
+ view.getLayoutParams().height = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.pick_contact_first_item_height);
+ photoView.setVisibility(View.VISIBLE);
+ view.findViewById(R.id.pick_contact_name).setVisibility(View.VISIBLE);
+ ((TextView) view.findViewById(R.id.pick_contact_name))
+ .setText(cache.name == null ? "" : cache.name);
+ } else {
+ view.getLayoutParams().height = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.pick_contact_same_item_height);
+ photoView.setVisibility(View.INVISIBLE);
+ view.findViewById(R.id.pick_contact_name).setVisibility(View.GONE);
+ }
+ }
+
+ private void setPhotoView(View view, Cursor cursor, ContactItemCache cache) {
+ ImageView photoView = ((ImageView) view
+ .findViewById(R.id.pick_contact_photo));
+ photoView.setVisibility(View.VISIBLE);
+
+ if (!cursor.isNull(SUMMARY_PHOTO_ID_COLUMN_INDEX)) {
+ cache.photoId = cursor.getLong(SUMMARY_PHOTO_ID_COLUMN_INDEX);
+ } else {
+ cache.photoId = 0;
+ }
+ if (!cursor.isNull(SUMMARY_CONTACT_COLUMN_PHOTO_URI)) {
+ cache.photoUri = UriUtils.parseUriOrNull(cursor
+ .getString(SUMMARY_CONTACT_COLUMN_PHOTO_URI));
+ } else {
+ cache.photoUri = null;
+ }
+ Account account = null;
+ if (!cursor.isNull(SUMMARY_ACCOUNT_TYPE)
+ && !cursor.isNull(SUMMARY_ACCOUNT_NAME)) {
+ final String accountType = cursor
+ .getString(SUMMARY_ACCOUNT_TYPE);
+ final String accountName = cursor
+ .getString(SUMMARY_ACCOUNT_NAME);
+ account = new Account(accountName, accountType);
+ }
+
+ if (cache.photoId != 0) {
+ mContactPhotoManager.loadThumbnail(photoView, cache.photoId, account, false, true,
+ null);
+ } else {
+ final Uri photoUri = cache.photoUri == null ? null : cache.photoUri;
+ DefaultImageRequest request = null;
+ if (photoUri == null) {
+ cache.lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
+ request = new DefaultImageRequest(cache.name, cache.lookupKey, true);
+ }
+ mContactPhotoManager.loadDirectoryPhoto(photoView, photoUri, account, false, true,
+ request);
+ }
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View v = null;
+ v = mInflater.inflate(R.layout.multi_pick_contact_item, parent, false);
+ ContactItemCache dataCache = new ContactItemCache();
+ v.setTag(dataCache);
+ return v;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View v;
+ if (!getCursor().moveToPosition(position)) {
+ throw new IllegalStateException("couldn't move cursor to position " + position);
+ }
+ if (convertView != null && convertView.getTag() != null) {
+ v = convertView;
+ } else {
+ v = newView(mContext, getCursor(), parent);
+ }
+ bindView(v, mContext, getCursor());
+ return v;
+ }
+
+ @Override
+ public void changeCursor(Cursor cursor) {
+ super.changeCursor(cursor);
+ updateIndexer(cursor);
+ }
+
+ private void bindSectionHeader(View view, int position) {
+ TextView section = (TextView) view.findViewById(R.id.section_index);
+ section.setVisibility(View.VISIBLE);
+ Placement placement = getItemPlacementInSection(position);
+ section.setText(placement.sectionHeader);
+ section.setTextAppearance(mContext, R.style.SectionHeaderStyle);
+ }
+
+ private void hideSectionHeader(View view) {
+ TextView section = (TextView) view.findViewById(R.id.section_index);
+ section.setVisibility(View.GONE);
+ }
+
+ /**
+ * Updates the indexer, which is used to produce section headers.
+ */
+ private void updateIndexer(Cursor cursor) {
+ if (cursor == null) {
+ setIndexer(null);
+ return;
+ }
+ Bundle bundle = cursor.getExtras();
+ if (bundle.containsKey(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES)
+ && bundle.containsKey(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS)) {
+ String sections[] = bundle.getStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
+ int counts[] = bundle.getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
+ setIndexer(new ContactsSectionIndexer(sections, counts));
+ } else {
+ setIndexer(null);
+ }
+ }
+
+ public void setIndexer(SectionIndexer index) {
+ mIndexer = index;
+ mPlacement.invalidate();
+ }
+
+ public int getPositionForSection(int sectionIndex) {
+ if (mIndexer != null) {
+ return mIndexer.getPositionForSection(sectionIndex);
+ }
+ return -1;
+ }
+
+ public Object[] getSections() {
+ if (mIndexer != null) {
+ return mIndexer.getSections();
+ } else {
+ return new String[] {" "};
+ }
+ }
+
+ public int getSectionForPosition(int position) {
+ if (mIndexer != null) {
+ return mIndexer.getSectionForPosition(position);
+ }
+ return -1;
+ }
+
+ /**
+ * Computes the item's placement within its section and populates the {@code placement}
+ * object accordingly. Please note that the returned object is volatile and should be copied
+ * if the result needs to be used later.
+ */
+ public Placement getItemPlacementInSection(int position) {
+ if (mPlacement.position == position) {
+ return mPlacement;
+ }
+ mPlacement.position = position;
+ int section = getSectionForPosition(position);
+ if (section != -1 && getPositionForSection(section) == position) {
+ mPlacement.firstInSection = true;
+ mPlacement.sectionHeader = (String) getSections()[section];
+ } else {
+ mPlacement.firstInSection = false;
+ mPlacement.sectionHeader = null;
+ }
+
+ mPlacement.lastInSection = (getPositionForSection(section + 1) - 1 == position);
+ return mPlacement;
+ }
+ }
+
+ protected static void log(String msg) {
+ if (DEBUG)
+ Log.d(TAG, msg);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+ mRootView = inflater.inflate(R.layout.multi_pick_contacts_fragment, container, false);
+ return mRootView;
+ }
+
+ /**
+ * @param isSelectedAll isSelectedAll is true, selected all contacts
+ * isSelectedAll is False, deselected all contacts
+ */
+ public void setSelectedAll(boolean isSelectedAll) {
+ Cursor cursor = mContactListAdapter.getCursor();
+ if (cursor == null) {
+ return;
+ }
+ ContactItemCache cache = new ContactItemCache();
+ String key;
+ // selected all contacts
+ if (isSelectedAll) {
+ int count = cursor.getCount();
+ for (int i = 0; i < count; i++) {
+ cursor.moveToPosition(i);
+ key = String.valueOf(cursor.getLong(0));
+ if (!mCheckListListener.onContainsKey(key)) {
+ String[] value = null;
+ if (mPickMode.isPickContact()) {
+ cache.lookupKey = cursor
+ .getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
+ cache.name = cursor
+ .getString(SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX);
+ cache.nameRawContactId = cursor
+ .getLong(SUMMARY_COLUMN_CONTACT_ID);
+ String photoUri = cursor.getString(SUMMARY_CONTACT_COLUMN_PHOTO_URI);
+ cache.accountType = cursor.getString(SUMMARY_ACCOUNT_TYPE);
+ cache.accountName = cursor.getString(SUMMARY_ACCOUNT_NAME);
+ cache.photoUri = UriUtils.parseUriOrNull(photoUri);
+ value = new String[] { cache.lookupKey, key,
+ String.valueOf(cache.nameRawContactId),
+ photoUri, cache.name, cache.accountType, cache.accountName};
+ } else if (mPickMode.isPickPhone()) {
+ cache.name = cursor
+ .getString(SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX);
+ cache.number = cursor.getString(DATA_DATA1_COLUMN);
+ cache.label = cursor.getString(DATA_DATA3_COLUMN);
+ cache.type = String.valueOf(cursor
+ .getInt(DATA_DATA2_COLUMN));
+ value = new String[] { cache.name, cache.number,
+ cache.type, cache.label, cache.contact_id };
+ } else if (mPickMode.isPickEmail()) {
+ cache.name = cursor
+ .getString(SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX);
+ cache.email = cursor.getString(DATA_DATA1_COLUMN);
+ value = new String[] { cache.name, cache.email };
+ }
+ mCheckListListener.putValue(key, value);
+ }
+ }
+ } else {
+ mCheckListListener.onClear();
+ }
+ // update actionbar selected button to display selected item numbers
+ mCheckListListener.onUpdateActionBar();
+ mContactListAdapter.notifyDataSetChanged();
+ }
+
+}
diff --git a/src/com/android/contacts/list/ContactsPickMode.java b/src/com/android/contacts/list/ContactsPickMode.java
new file mode 100644
index 0000000..b822513
--- /dev/null
+++ b/src/com/android/contacts/list/ContactsPickMode.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package com.android.contacts.list;
+
+import com.android.contacts.SimContactsConstants;
+
+import android.content.Intent;
+
+import com.android.contacts.R;
+
+
+/**
+ * Parsed form of the intent sent to the Contacts application.
+ */
+public class ContactsPickMode {
+
+ private static ContactsPickMode mPickMode;
+
+ private static final String MODE = "mode";
+
+ private int mMode;
+ private Intent mIntent;
+
+ private static final int MODE_MASK_SEARCH = 0x80000000;
+
+ public static final int MODE_DEFAULT_CONTACT = 0;
+ public static final int MODE_DEFAULT_PHONE = 1;
+ public static final int MODE_DEFAULT_EMAIL = 1 << 1;
+ public static final int MODE_SEARCH_CONTACT = MODE_DEFAULT_CONTACT | MODE_MASK_SEARCH;
+ public static final int MODE_SEARCH_PHONE = MODE_DEFAULT_PHONE | MODE_MASK_SEARCH;
+ public static final int MODE_SEARCH_EMAIL = MODE_DEFAULT_EMAIL | MODE_MASK_SEARCH;
+
+ public static ContactsPickMode getInstance() {
+ if (mPickMode == null) {
+ mPickMode = new ContactsPickMode();
+ }
+ return mPickMode;
+ }
+
+ public boolean isSearchMode() {
+ return (mMode & MODE_MASK_SEARCH) == MODE_MASK_SEARCH;
+ }
+
+ public boolean isPickContact() {
+ return mMode == MODE_DEFAULT_CONTACT || mMode == MODE_SEARCH_CONTACT;
+ }
+
+ public boolean isPickPhone() {
+ return mMode == MODE_DEFAULT_PHONE || mMode == MODE_SEARCH_PHONE;
+ }
+
+ public boolean isPickEmail() {
+ return mMode == MODE_DEFAULT_EMAIL || mMode == MODE_SEARCH_EMAIL;
+ }
+
+ public void enterSearchMode() {
+ mMode |= MODE_MASK_SEARCH;
+ }
+
+ public void exitSearchMode() {
+ mMode &= ~MODE_MASK_SEARCH;
+ }
+
+ public void setMode(int mode) {
+ mMode = mode;
+ }
+
+ public int getMode() {
+ return mMode;
+ }
+
+ public void setMode(Intent intent) {
+ mIntent = intent;
+ String action = intent.getAction();
+ if (SimContactsConstants.ACTION_MULTI_PICK.equals(action)) {
+ mMode = MODE_DEFAULT_PHONE;
+ } else if (SimContactsConstants.ACTION_MULTI_PICK_EMAIL.equals(action)) {
+ mMode = MODE_DEFAULT_EMAIL;
+ } else {
+ mMode = MODE_DEFAULT_CONTACT;
+ }
+ }
+
+ public Intent getIntent() {
+ return mIntent;
+ }
+}
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 9851d2b..9905c93 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -76,6 +76,7 @@
import com.android.contacts.util.ImplicitIntentsUtil;
import com.android.contacts.util.SharedPreferenceUtil;
import com.android.contacts.util.SyncUtil;
+import com.android.contacts.SimContactsConstants;
import com.android.contactsbind.FeatureHighlightHelper;
import com.android.contactsbind.experiments.Flags;
import com.google.common.util.concurrent.Futures;
@@ -1014,9 +1015,10 @@
final boolean showSelectedContactOptions = mActionBarAdapter.isSelectionMode()
&& getSelectedContactIds().size() != 0;
makeMenuItemVisible(menu, R.id.menu_share, showSelectedContactOptions);
- makeMenuItemVisible(menu, R.id.menu_delete, showSelectedContactOptions);
+ makeMenuItemVisible(menu, R.id.menu_delete, !isSearchOrSelectionMode);
final boolean showLinkContactsOptions = mActionBarAdapter.isSelectionMode()
- && getSelectedContactIds().size() > 1;
+ && getSelectedContactIds().size() > 1
+ && getSelectedSimContact() <1;
makeMenuItemVisible(menu, R.id.menu_join, showLinkContactsOptions);
// Debug options need to be visible even in search mode.
@@ -1078,7 +1080,11 @@
joinSelectedContacts();
return true;
} else if (id == R.id.menu_delete) {
- deleteSelectedContacts();
+ final Intent intent = new Intent(SimContactsConstants.ACTION_MULTI_PICK_CONTACT,
+ ContactsContract.Contacts.CONTENT_URI);
+ intent.putExtra(AccountFilterActivity.EXTRA_CONTACT_LIST_FILTER, getFilter());
+ intent.putExtra("delete", true);
+ startActivity(intent);
return true;
} else if (id == R.id.export_database) {
final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE");
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index 20c1a8f..19fb91b 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -27,10 +27,12 @@
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.SearchSnippets;
import android.text.TextUtils;
import android.view.View;
import com.android.contacts.compat.ContactsCompat;
+import com.android.contacts.SimContactsConstants;
import com.android.contacts.model.account.AccountWithDataSet;
import com.android.contacts.preference.ContactsPreferences;
import java.util.ArrayList;
@@ -89,12 +91,7 @@
} else {
final ContactListFilter filter = getFilter();
configureUri(loader, directoryId, filter);
- if (filter != null
- && filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
- loader.setProjection(getDataProjectionForContacts(false));
- } else {
- loader.setProjection(getProjection(false));
- }
+ loader.setProjection(getProjection(false));
configureSelection(loader, directoryId, filter);
}
@@ -140,8 +137,6 @@
} else {
uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, getSelectedContactId());
}
- } else if (filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
- uri = Data.CONTENT_URI;
}
}
@@ -220,10 +215,15 @@
selectionArgs.add(filter.accountName);
}
} else {
- selection.append(AccountWithDataSet.LOCAL_ACCOUNT_SELECTION);
+ selection.append(RawContacts.ACCOUNT_TYPE + " IS NULL");
}
break;
}
+ case ContactListFilter.FILTER_TYPE_ALL_WITHOUT_SIM:
+ selection.append(RawContacts.ACCOUNT_TYPE + "!= '"
+ + SimContactsConstants.ACCOUNT_TYPE_SIM + "'" + " OR "
+ + RawContacts.ACCOUNT_TYPE + " IS NULL");
+ break;
}
loader.setSelection(selection.toString());
loader.setSelectionArgs(selectionArgs.toArray(new String[0]));
@@ -245,7 +245,8 @@
if (isQuickContactEnabled()) {
bindQuickContact(view, partition, cursor, ContactQuery.CONTACT_PHOTO_ID,
ContactQuery.CONTACT_PHOTO_URI, ContactQuery.CONTACT_ID,
- ContactQuery.CONTACT_LOOKUP_KEY, ContactQuery.CONTACT_DISPLAY_NAME);
+ ContactQuery.CONTACT_LOOKUP_KEY, ContactQuery.CONTACT_DISPLAY_NAME,
+ ContactQuery.CONTACT_ACCOUNT_TYPE, ContactQuery.CONTACT_ACCOUNT_NAME);
} else {
if (getDisplayPhotos()) {
bindPhoto(view, partition, cursor);
diff --git a/src/com/android/contacts/list/JoinContactListAdapter.java b/src/com/android/contacts/list/JoinContactListAdapter.java
index 82da6f7..143fcf4 100644
--- a/src/com/android/contacts/list/JoinContactListAdapter.java
+++ b/src/com/android/contacts/list/JoinContactListAdapter.java
@@ -24,6 +24,7 @@
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Contacts.AggregationSuggestions;
import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,6 +32,7 @@
import android.widget.TextView;
import com.android.contacts.R;
+import com.android.contacts.model.account.SimAccountType;
import com.android.contacts.preference.ContactsPreferences;
public class JoinContactListAdapter extends ContactListAdapter {
@@ -97,7 +99,9 @@
.build();
}
loader.setUri(allContactsUri);
- loader.setSelection(Contacts._ID + "!=?");
+ loader.setSelection(Contacts._ID + "!=?" + " AND (" + RawContacts.ACCOUNT_TYPE + "!= '"
+ + SimAccountType.ACCOUNT_TYPE + "'" + " OR "
+ + RawContacts.ACCOUNT_TYPE + " IS NULL)");
loader.setSelectionArgs(new String[]{ String.valueOf(mTargetContactId) });
if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
loader.setSortOrder(Contacts.SORT_KEY_PRIMARY);
diff --git a/src/com/android/contacts/list/MultiSelectContactsListFragment.java b/src/com/android/contacts/list/MultiSelectContactsListFragment.java
index 5e7f9e8..657451f 100644
--- a/src/com/android/contacts/list/MultiSelectContactsListFragment.java
+++ b/src/com/android/contacts/list/MultiSelectContactsListFragment.java
@@ -44,6 +44,7 @@
import com.android.contacts.model.account.AccountType;
import com.android.contacts.model.account.AccountWithDataSet;
import com.android.contacts.model.account.GoogleAccountType;
+import com.android.contacts.model.account.SimAccountType;
import java.util.ArrayList;
import java.util.List;
@@ -67,6 +68,7 @@
}
private static final String EXTRA_KEY_SELECTED_CONTACTS = "selected_contacts";
+ private static final String EXTRA_KEY_SELECTED_SIM_CONTACT ="selected_sim";
private OnCheckBoxListActionListener mCheckBoxListListener;
@@ -99,7 +101,10 @@
if (savedInstanceState != null) {
final TreeSet<Long> selectedContactIds = (TreeSet<Long>)
savedInstanceState.getSerializable(EXTRA_KEY_SELECTED_CONTACTS);
+ final int selectSimContact = savedInstanceState
+ .getInt(EXTRA_KEY_SELECTED_SIM_CONTACT);
getAdapter().setSelectedContactIds(selectedContactIds);
+ getAdapter().setSelectedSimContact(selectSimContact);
}
}
@@ -119,6 +124,10 @@
return getAdapter().getSelectedContactIdsArray();
}
+ public int getSelectedSimContact() {
+ return getAdapter().getSelectedSimContact();
+ }
+
@Override
protected void configureAdapter() {
super.configureAdapter();
@@ -129,6 +138,7 @@
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(EXTRA_KEY_SELECTED_CONTACTS, getSelectedContactIds());
+ outState.putInt(EXTRA_KEY_SELECTED_SIM_CONTACT, getSelectedSimContact());
}
public void displayCheckBoxes(boolean displayCheckBoxes) {
@@ -149,11 +159,12 @@
final int previouslySelectedCount = getAdapter().getSelectedContactIds().size();
final long contactId = getContactId(position);
final int partition = getAdapter().getPartitionForPosition(position);
+ final boolean simContact = isSimContact(position);
if (contactId >= 0 && partition == ContactsContract.Directory.DEFAULT) {
if (mCheckBoxListListener != null) {
mCheckBoxListListener.onStartDisplayingCheckBoxes();
}
- getAdapter().toggleSelectionOfContactId(contactId);
+ getAdapter().toggleSelectionOfContactId(contactId, simContact);
Logger.logListEvent(ActionType.SELECT, getListType(),
/* count */ getAdapter().getCount(), /* clickedIndex */ position,
/* numSelected */ 1);
@@ -178,11 +189,12 @@
@Override
protected void onItemClick(int position, long id) {
final long contactId = getContactId(position);
+ final boolean simContact = isSimContact(position);
if (contactId < 0) {
return;
}
if (getAdapter().isDisplayingCheckBoxes()) {
- getAdapter().toggleSelectionOfContactId(contactId);
+ getAdapter().toggleSelectionOfContactId(contactId, simContact);
}
if (mCheckBoxListListener != null && getAdapter().getSelectedContactIds().size() == 0) {
mCheckBoxListListener.onStopDisplayingCheckBoxes();
@@ -203,6 +215,20 @@
return -1;
}
+ private boolean isSimContact(int position) {
+ final Cursor cursor = (Cursor) getAdapter().getItem(position);
+ final int accountTypeColumnIndex = cursor
+ .getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE);
+ if (cursor != null && accountTypeColumnIndex >= 0) {
+ if ((cursor.getColumnCount() > accountTypeColumnIndex)) {
+ final String accountType = cursor.getString(accountTypeColumnIndex);
+ return accountType != null
+ && accountType.equals(SimAccountType.ACCOUNT_TYPE);
+ }
+ }
+ return false;
+ }
+
/**
* Returns the state of the search results currently presented to the user.
*/
diff --git a/src/com/android/contacts/list/MultiSelectEntryContactListAdapter.java b/src/com/android/contacts/list/MultiSelectEntryContactListAdapter.java
index a0b4f3c..99aa056 100644
--- a/src/com/android/contacts/list/MultiSelectEntryContactListAdapter.java
+++ b/src/com/android/contacts/list/MultiSelectEntryContactListAdapter.java
@@ -16,6 +16,7 @@
package com.android.contacts.list;
+import android.accounts.Account;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract;
@@ -38,6 +39,8 @@
private TreeSet<Long> mSelectedContactIds = new TreeSet<>();
private boolean mDisplayCheckBoxes;
private final int mContactIdColumnIndex;
+ //indicate how many sim contact has been selected
+ private int mSimContactSelected = 0;
public interface SelectedContactsListener {
void onSelectedContactsChanged();
@@ -89,6 +92,14 @@
return mSelectedContactIds.size() > 0;
}
+ public int getSelectedSimContact() {
+ return mSimContactSelected;
+ }
+
+ public void setSelectedSimContact(int selectedCount) {
+ this.mSimContactSelected = selectedCount;
+ }
+
/**
* Returns the selected contacts as an array.
*/
@@ -130,10 +141,18 @@
* Toggle the checkbox beside the contact for {@param contactId}.
*/
public void toggleSelectionOfContactId(long contactId) {
+ toggleSelectionOfContactId(contactId, false);
+ }
+
+ public void toggleSelectionOfContactId(long contactId, boolean simContact) {
if (mSelectedContactIds.contains(contactId)) {
mSelectedContactIds.remove(contactId);
+ if(simContact)
+ mSimContactSelected --;
} else {
mSelectedContactIds.add(contactId);
+ if(simContact)
+ mSimContactSelected ++;
}
notifyDataSetChanged();
if (mSelectedContactsListener != null) {
@@ -165,15 +184,28 @@
* @param displayNameColumn Index of the display name column
*/
protected void bindPhoto(final ContactListItemView view, final Cursor cursor,
- final int photoIdColumn, final int lookUpKeyColumn, final int displayNameColumn) {
+ final int photoIdColumn, final int lookUpKeyColumn, final int displayNameColumn) {
+ bindPhoto(view, cursor, photoIdColumn, lookUpKeyColumn, displayNameColumn, -1, -1);
+ }
+
+ protected void bindPhoto(final ContactListItemView view, final Cursor cursor,
+ final int photoIdColumn, final int lookUpKeyColumn, final int displayNameColumn
+ , final int accountTypeColumn, final int accountNameColumn) {
final long photoId = cursor.isNull(photoIdColumn)
? 0 : cursor.getLong(photoIdColumn);
+ Account account = null;
+ if (!cursor.isNull(accountTypeColumn)
+ && !cursor.isNull(accountNameColumn)) {
+ final String accountType = cursor.getString(accountTypeColumn);
+ final String accountName = cursor.getString(accountNameColumn);
+ account = new Account(accountName, accountType);
+ }
final ContactPhotoManager.DefaultImageRequest imageRequest = photoId == 0
? getDefaultImageRequestFromCursor(cursor, displayNameColumn,
lookUpKeyColumn)
: null;
- getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false, getCircularPhotos(),
- imageRequest);
+ getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, account, false,
+ getCircularPhotos(), imageRequest);
}
private void bindCheckBox(ContactListItemView view, Cursor cursor, boolean isLocalDirectory) {
diff --git a/src/com/android/contacts/list/MultiSelectPhoneNumbersListAdapter.java b/src/com/android/contacts/list/MultiSelectPhoneNumbersListAdapter.java
index 30d5429..d06abfe 100644
--- a/src/com/android/contacts/list/MultiSelectPhoneNumbersListAdapter.java
+++ b/src/com/android/contacts/list/MultiSelectPhoneNumbersListAdapter.java
@@ -23,6 +23,7 @@
import android.net.Uri.Builder;
import android.os.Bundle;
import android.provider.ContactsContract;
+import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.text.TextUtils;
import android.view.View;
@@ -45,6 +46,8 @@
Phone.PHOTO_ID, // 6
Phone.DISPLAY_NAME_PRIMARY, // 7
Phone.PHOTO_THUMBNAIL_URI, // 8
+ RawContacts.ACCOUNT_TYPE, // 9
+ RawContacts.ACCOUNT_NAME, // 10
};
public static final String[] PROJECTION_ALTERNATIVE = new String[] {
@@ -57,6 +60,8 @@
Phone.PHOTO_ID, // 6
Phone.DISPLAY_NAME_ALTERNATIVE, // 7
Phone.PHOTO_THUMBNAIL_URI, // 8
+ RawContacts.ACCOUNT_TYPE, // 9
+ RawContacts.ACCOUNT_NAME, // 10
};
public static final int PHONE_ID = 0;
@@ -68,6 +73,8 @@
public static final int PHOTO_ID = 6;
public static final int DISPLAY_NAME = 7;
public static final int PHOTO_URI = 8;
+ public static final int ACCOUNT_TYPE = 9;
+ public static final int ACCOUNT_NAME = 10;
}
private final CharSequence mUnknownNameText;
@@ -160,7 +167,7 @@
if (isFirstEntry) {
bindName(view, cursor);
bindPhoto(view, cursor, PhoneQuery.PHOTO_ID, PhoneQuery.LOOKUP_KEY,
- PhoneQuery.DISPLAY_NAME);
+ PhoneQuery.DISPLAY_NAME, PhoneQuery.ACCOUNT_TYPE, PhoneQuery.ACCOUNT_NAME);
} else {
unbindName(view);
view.removePhotoView(true, false);
diff --git a/src/com/android/contacts/list/OnCheckListActionListener.java b/src/com/android/contacts/list/OnCheckListActionListener.java
new file mode 100644
index 0000000..0586df8
--- /dev/null
+++ b/src/com/android/contacts/list/OnCheckListActionListener.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.contacts.list;
+
+import java.util.List;
+
+public interface OnCheckListActionListener {
+
+ /**
+ * judge mChoiceSet contain key
+ *
+ * @key represent data id, contact id
+ */
+ boolean onContainsKey(String key);
+
+ /**
+ * put value to mChoiceSet
+ */
+ void putValue(String key, String[] value);
+
+ /**
+ * remove value from mChoiceSet
+ */
+ void onRemove(String key);
+
+ /**
+ * clear mChoiceSet
+ */
+ void onClear();
+
+ /**
+ * hide softkeyboard
+ */
+ void onHideSoftKeyboard();
+
+ /**
+ * update action bar
+ */
+ void onUpdateActionBar();
+
+ /**
+ * exit search mode
+ */
+ void exitSearch();
+
+
+}
diff --git a/src/com/android/contacts/list/PhoneNumberListAdapter.java b/src/com/android/contacts/list/PhoneNumberListAdapter.java
index d459d47..550bea5 100644
--- a/src/com/android/contacts/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/PhoneNumberListAdapter.java
@@ -15,6 +15,7 @@
*/
package com.android.contacts.list;
+import android.accounts.Account;
import android.content.ContentUris;
import android.content.Context;
import android.content.CursorLoader;
@@ -28,6 +29,7 @@
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -106,6 +108,8 @@
Phone.PHOTO_ID, // 6
Phone.DISPLAY_NAME_PRIMARY, // 7
Phone.PHOTO_THUMBNAIL_URI, // 8
+ RawContacts.ACCOUNT_TYPE, // 9
+ RawContacts.ACCOUNT_NAME, // 10
};
public static final String[] PROJECTION_PRIMARY;
@@ -128,6 +132,8 @@
Phone.PHOTO_ID, // 6
Phone.DISPLAY_NAME_ALTERNATIVE, // 7
Phone.PHOTO_THUMBNAIL_URI, // 8
+ RawContacts.ACCOUNT_TYPE, // 9
+ RawContacts.ACCOUNT_NAME, // 10
};
public static final String[] PROJECTION_ALTERNATIVE;
@@ -149,7 +155,9 @@
public static final int PHOTO_ID = 6;
public static final int DISPLAY_NAME = 7;
public static final int PHOTO_URI = 8;
- public static final int CARRIER_PRESENCE = 9;
+ public static final int ACCOUNT_TYPE = 9;
+ public static final int ACCOUNT_NAME = 10;
+ public static final int CARRIER_PRESENCE = 11;
}
private static final String IGNORE_NUMBER_TOO_LONG_CLAUSE =
@@ -447,7 +455,8 @@
if (isQuickContactEnabled()) {
bindQuickContact(view, partition, cursor, PhoneQuery.PHOTO_ID,
PhoneQuery.PHOTO_URI, PhoneQuery.CONTACT_ID,
- PhoneQuery.LOOKUP_KEY, PhoneQuery.DISPLAY_NAME);
+ PhoneQuery.LOOKUP_KEY, PhoneQuery.DISPLAY_NAME,
+ PhoneQuery.ACCOUNT_TYPE, PhoneQuery.ACCOUNT_NAME);
} else {
if (getDisplayPhotos()) {
bindPhoto(view, partition, cursor);
@@ -540,9 +549,15 @@
if (!cursor.isNull(PhoneQuery.PHOTO_ID)) {
photoId = cursor.getLong(PhoneQuery.PHOTO_ID);
}
-
+ Account account = null;
+ if (!cursor.isNull(PhoneQuery.ACCOUNT_TYPE)
+ && !cursor.isNull(PhoneQuery.ACCOUNT_NAME)) {
+ final String accountType = cursor.getString(PhoneQuery.ACCOUNT_TYPE);
+ final String accountName = cursor.getString(PhoneQuery.ACCOUNT_NAME);
+ account = new Account(accountName, accountType);
+ }
if (photoId != 0) {
- getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false,
+ getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, account, false,
getCircularPhotos(), null);
} else {
final String photoUriString = cursor.getString(PhoneQuery.PHOTO_URI);
@@ -554,7 +569,7 @@
final String lookupKey = cursor.getString(PhoneQuery.LOOKUP_KEY);
request = new DefaultImageRequest(displayName, lookupKey, getCircularPhotos());
}
- getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false,
+ getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, account, false,
getCircularPhotos(), request);
}
}
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index f67f074..8593e24 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -99,6 +99,13 @@
public boolean apply(@Nullable AccountInfo input) {
return input != null && input.getType().isGroupMembershipEditable();
}
+ },
+ CONTACTS_WRITABLE_WITHOUT_SIM {
+ @Override
+ public boolean apply(@Nullable AccountInfo input) {
+ return input != null && !input.getType().isSimAccount()
+ && input.getType().areContactsWritable();
+ }
};
}
@@ -190,6 +197,12 @@
Futures.getUnchecked(filterAccountsAsync(AccountFilter.CONTACTS_WRITABLE)));
}
+ public List<AccountWithDataSet> blockForWritableAccountsWithoutSim() {
+ return AccountInfo.extractAccounts(
+ Futures.getUnchecked(filterAccountsAsync(AccountFilter
+ .CONTACTS_WRITABLE_WITHOUT_SIM)));
+ }
+
/**
* Loads accounts in background and returns future that will complete with list of all accounts
*/
diff --git a/src/com/android/contacts/model/Contact.java b/src/com/android/contacts/model/Contact.java
index 8266123..58ac76a 100644
--- a/src/com/android/contacts/model/Contact.java
+++ b/src/com/android/contacts/model/Contact.java
@@ -81,6 +81,9 @@
private String mDirectoryAccountName;
private int mDirectoryExportSupport;
+ private String mAccountType;
+ private String mAccountName;
+
private ImmutableList<GroupMetaData> mGroups;
private byte[] mPhotoBinaryData;
@@ -417,6 +420,14 @@
return mThumbnailPhotoBinaryData;
}
+ public String getAccountType() {
+ return mAccountType;
+ }
+
+ public String getAccountName() {
+ return mAccountName;
+ }
+
public ArrayList<ContentValues> getContentValues() {
if (mRawContacts.size() != 1) {
throw new IllegalStateException(
@@ -493,4 +504,9 @@
/* package */ void setGroupMetaData(ImmutableList<GroupMetaData> groups) {
mGroups = groups;
}
+
+ public void setAccountInfo(String accountType, String accountName) {
+ mAccountType = accountType;
+ mAccountName = accountName;
+ }
}
diff --git a/src/com/android/contacts/model/ContactLoader.java b/src/com/android/contacts/model/ContactLoader.java
index bf1ac74..ea439e7 100644
--- a/src/com/android/contacts/model/ContactLoader.java
+++ b/src/com/android/contacts/model/ContactLoader.java
@@ -479,6 +479,13 @@
currentRawContactId = rawContactId;
rawContact = new RawContact(loadRawContactValues(cursor));
rawContactsBuilder.add(rawContact);
+ if (currentRawContactId == contact.getNameRawContactId()) {
+ final String accountType = cursor
+ .getString(ContactQuery.ACCOUNT_TYPE);
+ final String accountName = cursor
+ .getString(ContactQuery.ACCOUNT_NAME);
+ contact.setAccountInfo(accountType, accountName);
+ }
}
if (!cursor.isNull(ContactQuery.DATA_ID)) {
ContentValues data = loadDataValues(cursor);
diff --git a/src/com/android/contacts/model/RawContactDelta.java b/src/com/android/contacts/model/RawContactDelta.java
index 6ee31ba..8bb36d4 100644
--- a/src/com/android/contacts/model/RawContactDelta.java
+++ b/src/com/android/contacts/model/RawContactDelta.java
@@ -27,12 +27,18 @@
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Profile;
import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.text.TextUtils;
import android.util.Log;
+import com.android.contacts.SimContactsConstants;
+import com.android.contacts.model.ValuesDelta;
import com.android.contacts.compat.CompatUtils;
import com.android.contacts.model.account.AccountType;
import com.android.contacts.model.account.AccountWithDataSet;
-
+import com.android.contacts.model.account.SimAccountType;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -87,11 +93,14 @@
* starting point; the "before" snapshot.
*/
public static RawContactDelta fromBefore(RawContact before) {
+ String account = before.getAccountTypeString();
+ boolean isSimContact = (account != null && account
+ .equals(SimAccountType.ACCOUNT_TYPE)) ? true : false;
final RawContactDelta rawContactDelta = new RawContactDelta();
rawContactDelta.mValues = ValuesDelta.fromBefore(before.getValues());
rawContactDelta.mValues.setIdColumn(RawContacts._ID);
for (final ContentValues values : before.getContentValues()) {
- rawContactDelta.addEntry(ValuesDelta.fromBefore(values));
+ rawContactDelta.addEntry(ValuesDelta.fromBefore(values, isSimContact));
}
return rawContactDelta;
}
@@ -446,6 +455,119 @@
return builder;
}
+ public ContentValues buildSimDiff() {
+ ContentValues values = new ContentValues();
+ ArrayList<ValuesDelta> names = getMimeEntries(StructuredName.CONTENT_ITEM_TYPE);
+ ArrayList<ValuesDelta> phones = getMimeEntries(Phone.CONTENT_ITEM_TYPE);
+ ArrayList<ValuesDelta> emails = getMimeEntries(Email.CONTENT_ITEM_TYPE);
+ ValuesDelta nameValuesDelta = null;
+ ValuesDelta emailValuesDelta = null;
+
+ if (names != null && names.size() > 0)
+ nameValuesDelta = names.get(0);
+ if (emails != null && emails.size() > 0) {
+ emailValuesDelta = emails.get(0);
+ }
+
+ String name = null;
+ String number = null;
+ String newName = null;
+ String newNumber = null;
+ StringBuilder email = new StringBuilder();
+ StringBuilder anr = new StringBuilder();
+ StringBuilder newEmail = new StringBuilder();
+ StringBuilder newAnr = new StringBuilder();
+
+ if (nameValuesDelta != null) {
+ if (isContactInsert()) {
+ name = nameValuesDelta.getAsString(StructuredName.GIVEN_NAME);
+ } else {
+ if (nameValuesDelta.getBefore() != null) {
+ name = nameValuesDelta.getBefore()
+ .getAsString(StructuredName.GIVEN_NAME);
+ }
+ if (nameValuesDelta.getAfter() != null) {
+ newName = nameValuesDelta.getAfter()
+ .getAsString(StructuredName.GIVEN_NAME);
+ }
+ }
+ }
+ if (isContactInsert() && phones != null) {
+ for (ValuesDelta valuesDelta : phones) {
+ if (valuesDelta.getAfter() != null
+ && valuesDelta.getAfter().size() != 0) {
+ if (Phone.TYPE_MOBILE == valuesDelta.getAfter().getAsLong(Phone.TYPE)) {
+ number = valuesDelta.getAfter().getAsString(Phone.NUMBER);
+ } else {
+ anr.append(valuesDelta.getAfter().getAsString(Phone.NUMBER));
+ anr.append(SimContactsConstants.ANR_SEP);
+ }
+ }
+ }
+ } else if (phones != null) {
+ for (ValuesDelta valuesDelta : phones) {
+ if (valuesDelta.getBefore() != null
+ && valuesDelta.getBefore().size() != 0) {
+ if (Phone.TYPE_MOBILE == valuesDelta.getBefore().getAsLong(Phone.TYPE) ) {
+ number = valuesDelta.getBefore().getAsString(Phone.NUMBER);
+ } else {
+ anr.append(valuesDelta.getBefore().getAsString(Phone.NUMBER));
+ anr.append(SimContactsConstants.ANR_SEP);
+ }
+ }
+ if (valuesDelta.getAfter() != null
+ && valuesDelta.getAfter().size() != 0) {
+ if (Phone.TYPE_MOBILE == valuesDelta.getAsLong(Phone.TYPE)) {
+ newNumber = valuesDelta.getAfter().getAsString(Phone.NUMBER);
+ } else {
+ newAnr.append(valuesDelta.getAfter().getAsString(Phone.NUMBER));
+ newAnr.append(SimContactsConstants.ANR_SEP);
+ }
+ }
+ }
+ }
+
+ if (isContactInsert() && emails != null) {
+ for (ValuesDelta valuesDelta : emails) {
+ if (valuesDelta.getAfter() != null
+ && valuesDelta.getAfter().size() != 0) {
+ email.append(valuesDelta.getAfter().getAsString(Email.DATA));
+ email.append(SimContactsConstants.EMAIL_SEP);
+ }
+ }
+ } else if (emails != null) {
+ for (ValuesDelta valuesDelta : emails) {
+ if (valuesDelta.getBefore() != null
+ && valuesDelta.getBefore().size() != 0) {
+ email.append(valuesDelta.getBefore().getAsString(Email.DATA));
+ email.append(SimContactsConstants.EMAIL_SEP);
+ }
+ if (valuesDelta.getAfter() != null
+ && valuesDelta.getAfter().size() != 0) {
+ newEmail.append(valuesDelta.getAfter().getAsString(Email.DATA));
+ newEmail.append(SimContactsConstants.EMAIL_SEP);
+ }
+ }
+ }
+
+ if (isContactInsert()) {
+ values.put(SimContactsConstants.STR_TAG, name);
+ values.put(SimContactsConstants.STR_NUMBER, number);
+ values.put(SimContactsConstants.STR_EMAILS, email.toString());
+ values.put(SimContactsConstants.STR_ANRS, anr.toString());
+ } else {
+ values.put(SimContactsConstants.STR_TAG, name);
+ values.put(SimContactsConstants.STR_NUMBER, number);
+ values.put(SimContactsConstants.STR_EMAILS, email.toString());
+ values.put(SimContactsConstants.STR_ANRS, anr.toString());
+ values.put(SimContactsConstants.STR_NEW_TAG, newName);
+ values.put(SimContactsConstants.STR_NEW_NUMBER, newNumber);
+ values.put(SimContactsConstants.STR_NEW_EMAILS, newEmail.toString());
+ values.put(SimContactsConstants.STR_NEW_ANRS, newAnr.toString());
+ }
+ return values;
+ }
+
/**
* Build a list of {@link ContentProviderOperation} that will transform the
* current "before" {@link Entity} state into the modified state which this
diff --git a/src/com/android/contacts/model/RawContactModifier.java b/src/com/android/contacts/model/RawContactModifier.java
old mode 100644
new mode 100755
index 789bd10..171a810
--- a/src/com/android/contacts/model/RawContactModifier.java
+++ b/src/com/android/contacts/model/RawContactModifier.java
@@ -1038,10 +1038,30 @@
return;
}
+ boolean supportPrefix = false;
+ boolean supportFamilyName = false;
+ boolean supportMiddleName = false;
+ boolean supportGivenName = false;
+ boolean supportSuffix = false;
boolean supportPhoneticFamilyName = false;
boolean supportPhoneticMiddleName = false;
boolean supportPhoneticGivenName = false;
for (EditField editField : newDataKind.fieldList) {
+ if (StructuredName.PREFIX.equals(editField.column)) {
+ supportPrefix = true;
+ }
+ if (StructuredName.FAMILY_NAME.equals(editField.column)) {
+ supportFamilyName = true;
+ }
+ if (StructuredName.MIDDLE_NAME.equals(editField.column)) {
+ supportMiddleName = true;
+ }
+ if (StructuredName.GIVEN_NAME.equals(editField.column)) {
+ supportGivenName = true;
+ }
+ if (StructuredName.SUFFIX.equals(editField.column)) {
+ supportSuffix = true;
+ }
if (StructuredName.PHONETIC_FAMILY_NAME.equals(editField.column)) {
supportPhoneticFamilyName = true;
}
@@ -1053,6 +1073,21 @@
}
}
+ if (!supportPrefix) {
+ values.remove(StructuredName.PREFIX);
+ }
+ if (!supportFamilyName) {
+ values.remove(StructuredName.FAMILY_NAME);
+ }
+ if (!supportMiddleName) {
+ values.remove(StructuredName.MIDDLE_NAME);
+ }
+ if (!supportGivenName) {
+ values.remove(StructuredName.GIVEN_NAME);
+ }
+ if (!supportSuffix) {
+ values.remove(StructuredName.SUFFIX);
+ }
if (!supportPhoneticFamilyName) {
values.remove(StructuredName.PHONETIC_FAMILY_NAME);
}
diff --git a/src/com/android/contacts/model/SimContact.java b/src/com/android/contacts/model/SimContact.java
index 7babe27..d6cc13e 100644
--- a/src/com/android/contacts/model/SimContact.java
+++ b/src/com/android/contacts/model/SimContact.java
@@ -47,20 +47,26 @@
private final String mName;
private final String mPhone;
private final String[] mEmails;
+ private final String[] mAnrs;
public SimContact(long id, String name, String phone) {
this(id, name, phone, null);
}
public SimContact(long id, String name, String phone, String[] emails) {
+ this(id, name, phone, emails, null);
+ }
+
+ public SimContact(long id, String name, String phone, String[] emails, String[] anrs) {
mId = id;
mName = name;
mPhone = phone == null ? "" : phone.trim();
mEmails = emails;
+ mAnrs = anrs;
}
public SimContact(SimContact other) {
- this(other.mId, other.mName, other.mPhone, other.mEmails);
+ this(other.mId, other.mName, other.mPhone, other.mEmails, other.mAnrs);
}
public long getId() {
@@ -79,10 +85,14 @@
return mEmails;
}
+ public String[] getAnrs() {
+ return mAnrs;
+ }
+
public void appendCreateContactOperations(List<ContentProviderOperation> ops,
AccountWithDataSet targetAccount) {
// There is nothing to save so skip it.
- if (!hasName() && !hasPhone() && !hasEmails()) return;
+ if (!hasName() && !hasPhone() && !hasEmails() && !hasAnrs()) return;
final int rawContactOpIndex = ops.size();
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
@@ -105,6 +115,12 @@
Email.ADDRESS, email));
}
}
+ if (mAnrs != null) {
+ for (String anr : mAnrs) {
+ ops.add(createInsertOp(rawContactOpIndex, Phone.CONTENT_ITEM_TYPE,
+ Phone.NUMBER, anr));
+ }
+ }
}
private ContentProviderOperation createInsertOp(int rawContactOpIndex, String mimeType,
@@ -134,6 +150,10 @@
return mEmails != null && mEmails.length > 0;
}
+ public boolean hasAnrs() {
+ return mAnrs != null && mAnrs.length > 0;
+ }
+
/**
* Generate a "fake" lookup key. This is needed because
* {@link ContactPhotoManager} will only generate a letter avatar
@@ -167,7 +187,8 @@
final SimContact that = (SimContact) o;
return mId == that.mId && Objects.equals(mName, that.mName) &&
- Objects.equals(mPhone, that.mPhone) && Arrays.equals(mEmails, that.mEmails);
+ Objects.equals(mPhone, that.mPhone) && Arrays.equals(mEmails, that.mEmails)
+ && Arrays.equals(mAnrs, that.mAnrs);
}
@Override
@@ -176,6 +197,7 @@
result = 31 * result + (mName != null ? mName.hashCode() : 0);
result = 31 * result + (mPhone != null ? mPhone.hashCode() : 0);
result = 31 * result + Arrays.hashCode(mEmails);
+ result = 31 * result + Arrays.hashCode(mAnrs);
return result;
}
@@ -190,6 +212,7 @@
dest.writeString(mName);
dest.writeString(mPhone);
dest.writeStringArray(mEmails);
+ dest.writeStringArray(mAnrs);
}
public static final Creator<SimContact> CREATOR = new Creator<SimContact>() {
@@ -199,7 +222,8 @@
final String name = source.readString();
final String phone = source.readString();
final String[] emails = source.createStringArray();
- return new SimContact(id, name, phone, emails);
+ final String[] anrs = source.createStringArray();
+ return new SimContact(id, name, phone, emails, anrs);
}
@Override
diff --git a/src/com/android/contacts/model/ValuesDelta.java b/src/com/android/contacts/model/ValuesDelta.java
index 120c205..bdeaf0d 100644
--- a/src/com/android/contacts/model/ValuesDelta.java
+++ b/src/com/android/contacts/model/ValuesDelta.java
@@ -59,9 +59,30 @@
* "before" state, usually from an {@link Entity}.
*/
public static ValuesDelta fromBefore(ContentValues before) {
+ return fromBefore(before, false);
+ }
+
+ public static ValuesDelta fromBefore(ContentValues before, boolean simContact) {
final ValuesDelta entry = new ValuesDelta();
entry.mBefore = before;
entry.mAfter = new ContentValues();
+ if (simContact) {
+ // init data1 to mAfter map for sim contacts
+ if (before.containsKey(ContactsContract.Data.DATA1)) {
+ String contactInfo = before
+ .getAsString(ContactsContract.Data.DATA1);
+ if (null != contactInfo && !"".equals(contactInfo)) {
+ entry.mAfter.put(ContactsContract.Data.DATA1, contactInfo);
+ }
+ }
+ if (before.containsKey(ContactsContract.Data.DATA2)) {
+ String contactInfo = before
+ .getAsString(ContactsContract.Data.DATA2);
+ if (null != contactInfo && !"".equals(contactInfo)) {
+ entry.mAfter.put(ContactsContract.Data.DATA2, contactInfo);
+ }
+ }
+ }
return entry;
}
diff --git a/src/com/android/contacts/model/account/AccountType.java b/src/com/android/contacts/model/account/AccountType.java
index c3e0eb5..9d9bf1f 100644
--- a/src/com/android/contacts/model/account/AccountType.java
+++ b/src/com/android/contacts/model/account/AccountType.java
@@ -289,6 +289,10 @@
}
}
+ public boolean isSimAccount() {
+ return false;
+ }
+
/**
* Whether or not groups created under this account type have editable membership lists.
*/
diff --git a/src/com/android/contacts/model/account/AccountTypeProvider.java b/src/com/android/contacts/model/account/AccountTypeProvider.java
index 4f83ec6..2657f29 100644
--- a/src/com/android/contacts/model/account/AccountTypeProvider.java
+++ b/src/com/android/contacts/model/account/AccountTypeProvider.java
@@ -178,6 +178,8 @@
+ ", packageName=" + auth.packageName);
}
accountType = mLocalAccountTypeFactory.getAccountType(type);
+ } else if (SimAccountType.ACCOUNT_TYPE.equals(type)) {
+ accountType = new SimAccountType(mContext, auth.packageName);
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Registering external account type=" + type
diff --git a/src/com/android/contacts/model/account/SimAccountType.java b/src/com/android/contacts/model/account/SimAccountType.java
index 360e944..9910005 100644
--- a/src/com/android/contacts/model/account/SimAccountType.java
+++ b/src/com/android/contacts/model/account/SimAccountType.java
@@ -17,6 +17,8 @@
import android.accounts.AuthenticatorDescription;
import android.content.Context;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
@@ -32,19 +34,22 @@
*/
public class SimAccountType extends BaseAccountType {
+ public static final String ACCOUNT_TYPE = "com.android.sim";
+
public SimAccountType(Context context) {
- this.titleRes = R.string.account_sim;
- this.iconRes = R.drawable.quantum_ic_sim_card_vd_theme_24;
+ this(context, null);
+ }
+
+ public SimAccountType(Context context, String resPackageName) {
+ this.accountType = ACCOUNT_TYPE;
+ this.resourcePackageName = resPackageName;
+ this.syncAdapterPackageName = resPackageName;
try {
addDataKindStructuredName(context);
addDataKindName(context);
- final DataKind phoneKind = addDataKindPhone(context);
- phoneKind.typeOverallMax = 1;
- // SIM card contacts don't necessarily support separate types (based on data exposed
- // in Samsung and LG Contacts Apps.
- phoneKind.typeList = Collections.emptyList();
-
+ addDataKindPhone(context);
+ addDataKindEmail(context);
mIsInitialized = true;
} catch (DefinitionException e) {
// Just fail fast. Because we're explicitly adding the fields in this class this
@@ -64,8 +69,14 @@
}
@Override
+ public boolean isSimAccount() {
+ return true;
+ }
+
+ @Override
public void initializeFieldsFromAuthenticator(AuthenticatorDescription authenticator) {
// Do nothing. We want to use our local icon and title
+ super.initializeFieldsFromAuthenticator(authenticator);
}
@Override
@@ -75,14 +86,9 @@
kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
kind.typeOverallMax = 1;
-
-
kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
+ kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.nameLabelsGroup,
FLAGS_PERSON_NAME));
- kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
- FLAGS_PERSON_NAME));
-
return kind;
}
@@ -98,18 +104,33 @@
context.getResources().getBoolean(R.bool.config_editor_field_order_primary);
kind.fieldList = Lists.newArrayList();
- if (!displayOrderPrimary) {
- kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
- FLAGS_PERSON_NAME));
- kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
- FLAGS_PERSON_NAME));
- } else {
- kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
- FLAGS_PERSON_NAME));
- kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
- FLAGS_PERSON_NAME));
- }
+ kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME,
+ R.string.nameLabelsGroup, FLAGS_PERSON_NAME));
+ return kind;
+ }
+ @Override
+ protected DataKind addDataKindPhone(Context context) throws DefinitionException {
+ final DataKind kind = super.addDataKindPhone(context);
+ kind.typeOverallMax = 2;
+ kind.typeColumn = Phone.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_HOME));// This is used to save ANR records
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind addDataKindEmail(Context context) throws DefinitionException {
+ final DataKind kind = super.addDataKindEmail(context);
+
+ kind.typeOverallMax = 1;
+ kind.typeList = Collections.emptyList();
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Email.ADDRESS, R.string.emailLabelsGroup, FLAGS_EMAIL));
return kind;
}
@@ -118,7 +139,7 @@
// Use the "SIM" type label for the name as well because on OEM phones the "name" is
// not always user-friendly
return new AccountInfo(
- new AccountDisplayInfo(account, getDisplayLabel(context), getDisplayLabel(context),
+ new AccountDisplayInfo(account, account.name, getDisplayLabel(context),
getDisplayIcon(context), true), this);
}
}
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index fcbfba9..ebc5f39 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -37,6 +37,7 @@
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.res.Resources;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
@@ -52,6 +53,8 @@
import android.os.Bundle;
import android.os.Trace;
import android.provider.CalendarContract;
+import android.os.Handler;
+import android.os.Message;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
@@ -79,6 +82,8 @@
import android.text.SpannableString;
import android.text.TextDirectionHeuristics;
import android.text.TextUtils;
+import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionManager;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
@@ -113,6 +118,9 @@
import com.android.contacts.compat.CompatUtils;
import com.android.contacts.compat.EventCompat;
import com.android.contacts.compat.MultiWindowCompat;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.ContactUtils;
+import com.android.contacts.SimContactsConstants;
import com.android.contacts.detail.ContactDisplayUtils;
import com.android.contacts.dialog.CallSubjectDialog;
import com.android.contacts.editor.ContactEditorFragment;
@@ -132,6 +140,7 @@
import com.android.contacts.model.ContactLoader;
import com.android.contacts.model.RawContact;
import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.SimAccountType;
import com.android.contacts.model.dataitem.CustomDataItem;
import com.android.contacts.model.dataitem.DataItem;
import com.android.contacts.model.dataitem.DataKind;
@@ -316,6 +325,7 @@
private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
+ private TelephonyManager tm;
/**
* {@link #LEADING_MIMETYPES} is used to sort MIME-types.
*
@@ -351,6 +361,8 @@
QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_SIP_NUMBERS";
private static final String FRAGMENT_TAG_SELECT_ACCOUNT = "select_account_fragment";
+ private boolean simOneLoadComplete = false;
+ private boolean simTwoLoadComplete = false;
final OnClickListener mEntryClickHandler = new OnClickListener() {
@Override
@@ -657,6 +669,7 @@
if (CompatUtils.isLollipopCompatible()) {
getWindow().setStatusBarColor(Color.TRANSPARENT);
}
+ tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
processIntent(getIntent());
@@ -2238,8 +2251,13 @@
if (displayName == null) {
displayName = getString(R.string.missing_name);
}
+ Account account = null;
+ if (mContactData.getAccountName() != null && mContactData.getAccountType() != null) {
+ account = new Account(mContactData.getAccountName(),
+ mContactData.getAccountType());
+ }
final ShortcutInfo shortcutInfo = shortcuts.getQuickContactShortcutInfo(
- mContactData.getId(), mContactData.getLookupKey(), displayName);
+ mContactData.getId(), mContactData.getLookupKey(), displayName, account);
if (shortcutInfo != null) {
shortcutManager.requestPinShortcut(shortcutInfo, null);
}
@@ -2311,7 +2329,17 @@
ContactDisplayUtils.configureStarredMenuItem(starredMenuItem,
mContactData.isDirectoryEntry(), mContactData.isUserProfile(),
mContactData.getStarred());
-
+ if (!simOneLoadComplete) {
+ simOneLoadComplete = (ContactUtils.getAdnRecordsCapacity(this,
+ SimContactsConstants.SLOT1)[0] > 0) ? true : false;
+ }
+ if (!simTwoLoadComplete) {
+ simTwoLoadComplete = (ContactUtils.getAdnRecordsCapacity(this,
+ SimContactsConstants.SLOT2)[0] > 0)? true : false;
+ }
+ final String accountType = mContactData.getAccountType();
+ boolean simContact = accountType != null && accountType
+ .equals(SimAccountType.ACCOUNT_TYPE);
// Configure edit MenuItem
final MenuItem editMenuItem = menu.findItem(R.id.menu_edit);
editMenuItem.setVisible(true);
@@ -2330,7 +2358,8 @@
final MenuItem joinMenuItem = menu.findItem(R.id.menu_join);
joinMenuItem.setVisible(!InvisibleContactUtil.isInvisibleAndAddable(mContactData, this)
&& isContactEditable() && !mContactData.isUserProfile()
- && !mContactData.isMultipleRawContacts());
+ && !mContactData.isMultipleRawContacts()
+ && !simContact);
// Viewing linked contacts can only happen if there are multiple raw contacts and
// the link menu isn't available.
@@ -2363,6 +2392,70 @@
final MenuItem helpMenu = menu.findItem(R.id.menu_help);
helpMenu.setVisible(HelpUtils.isHelpAndFeedbackAvailable());
+ final RawContact rawContact = mContactData.getRawContacts().get(0);
+ String accoutName = rawContact.getAccountName();
+ String accoutType = rawContact.getAccountTypeString();
+
+ final MenuItem copyToPhoneMenu = menu.findItem(R.id.menu_copy_to_phone);
+ if (copyToPhoneMenu != null) {
+ copyToPhoneMenu.setVisible(false);
+ }
+
+ final MenuItem copyToSim1Menu = menu.findItem(R.id.menu_copy_to_sim1);
+ if (copyToSim1Menu != null) {
+ copyToSim1Menu.setVisible(false);
+ }
+
+ final MenuItem copyToSim2Menu = menu.findItem(R.id.menu_copy_to_sim2);
+ if (copyToSim2Menu != null) {
+ copyToSim2Menu.setVisible(false);
+ }
+
+ if (!TextUtils.isEmpty(accoutType)) {
+ if (SimContactsConstants.ACCOUNT_TYPE_SIM.equals(accoutType)) {
+ copyToPhoneMenu.setVisible(true);
+ copyToPhoneMenu.setTitle(getString(R.string.menu_copyTo)
+ + getString(R.string.phoneLabelsGroup));
+ if (tm.getPhoneCount() > 1) {
+ if (SimContactsConstants.SIM_NAME_1.equals(accoutName)
+ && simTwoLoadComplete) {
+ copyToSim2Menu.setTitle(getString(R.string.menu_copyTo)
+ + ContactUtils.getAcount( QuickContactActivity.this,
+ SimContactsConstants.SLOT2).name);
+ copyToSim2Menu.setVisible(true);
+ }
+ if (SimContactsConstants.SIM_NAME_2.equals(accoutName)
+ && simOneLoadComplete) {
+ copyToSim1Menu.setTitle(getString(R.string.menu_copyTo)
+ + ContactUtils.getAcount(QuickContactActivity.this,
+ SimContactsConstants.SLOT1).name);
+ copyToSim1Menu.setVisible(true);
+ }
+ }
+ }
+ } else {
+ copyToPhoneMenu.setVisible(false);
+ if (tm.getPhoneCount() > 1) {
+ if (simOneLoadComplete) {
+ copyToSim1Menu.setTitle(getString(R.string.menu_copyTo)
+ + ContactUtils.getAcount(
+ this, SimContactsConstants.SLOT1).name);
+ copyToSim1Menu.setVisible(true);
+ }
+ if (simTwoLoadComplete) {
+ copyToSim2Menu.setTitle(getString(R.string.menu_copyTo)
+ + ContactUtils.getAcount(
+ this, SimContactsConstants.SLOT2).name);
+ copyToSim2Menu.setVisible(true);
+ }
+ } else {
+ if (simOneLoadComplete) {
+ copyToSim1Menu.setTitle(getString(R.string.menu_copyTo)
+ + SimContactsConstants.SIM_NAME);
+ copyToSim1Menu.setVisible(true);
+ }
+ }
+ }
return true;
}
return false;
@@ -2479,6 +2572,18 @@
Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
ActionType.HELP, /* thirdPartyAction */ null);
HelpUtils.launchHelpAndFeedbackForContactScreen(this);
+ } else if(id == R.id.menu_copy_to_phone) {
+ if (mContactData == null) return false;
+ copyToPhone();
+ return true;
+ } else if(id == R.id.menu_copy_to_sim1) {
+ if (mContactData == null) return false;
+ copyToCard(SimContactsConstants.SLOT1);
+ return true;
+ } else if(id == R.id.menu_copy_to_sim2) {
+ if (mContactData == null) return false;
+ copyToCard(SimContactsConstants.SLOT2);
+ return true;
} else {
Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
@@ -2487,6 +2592,254 @@
return true;
}
+ //supply phone number and email which could stored in one ADN
+ class UsimEntity {
+ private ArrayList<String> mNumberList = new ArrayList<String>();
+ private ArrayList<String> mEmailList = new ArrayList<String>();
+
+ public ArrayList<String> getEmailList() {
+ return mEmailList;
+ }
+
+ public ArrayList<String> getNumberList() {
+ return mNumberList;
+ }
+
+ public void putEmailList(ArrayList<String> list) {
+ mEmailList = list;
+ }
+
+ public void putNumberList(ArrayList<String> list) {
+ mNumberList = list;
+ }
+ }
+
+ private void copyToPhone() {
+ String name = mContactData.getDisplayName();
+ if (TextUtils.isEmpty(name)) {
+ name = "";
+ }
+ String phoneNumber = "";
+ StringBuilder anrNumber = new StringBuilder();
+ StringBuilder email = new StringBuilder();
+
+ //get phonenumber,email,anr from SIM contacts,then insert them to phone
+ for (RawContact rawContact : mContactData.getRawContacts()) {
+ for (DataItem dataItem : rawContact.getDataItems()) {
+ if (dataItem.getMimeType() == null) {
+ continue;
+ }
+ if (dataItem instanceof PhoneDataItem) {
+ PhoneDataItem phoneNum = (PhoneDataItem) dataItem;
+ final String number = phoneNum.getNumber();
+ if (!TextUtils.isEmpty(number)) {
+ if (Phone.TYPE_MOBILE == phoneNum.getContentValues().getAsInteger(
+ Phone.TYPE)) {
+ phoneNumber = number;
+ } else {
+ if(!TextUtils.isEmpty(anrNumber.toString())) {
+ anrNumber.append(SimContactsConstants.ANR_SEP);
+ }
+ anrNumber.append(number);
+ }
+ }
+ } else if (dataItem instanceof EmailDataItem) {
+ EmailDataItem emailData = (EmailDataItem) dataItem;
+ final String address = emailData.getData();
+ if (!TextUtils.isEmpty(address)) {
+ if(!TextUtils.isEmpty(email.toString())) {
+ email.append(SimContactsConstants.EMAIL_SEP);
+ }
+ email.append(address);
+ }
+ }
+ }
+ }
+
+ String[] value = new String[] {
+ name, phoneNumber, email.toString(), anrNumber.toString()
+ };
+ boolean success = ContactUtils
+ .insertToPhone(value, QuickContactActivity.this,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ Toast.makeText(this, success ? R.string.copy_done : R.string.copy_failure,
+ Toast.LENGTH_SHORT).show();
+ }
+
+ private Handler mHandler = null;
+
+ private void copyToCard(final int sub) {
+ final int MSG_COPY_DONE = 0;
+ final int MSG_COPY_FAILURE = 1;
+ final int MSG_CARD_NO_SPACE = 2;
+ final int MSG_NO_EMPTY_EMAIL = 3;
+ if (mHandler == null) {
+ mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_COPY_DONE:
+ Toast.makeText(QuickContactActivity.this, R.string.copy_done,
+ Toast.LENGTH_SHORT).show();
+ break;
+ case MSG_COPY_FAILURE:
+ Toast.makeText(QuickContactActivity.this, R.string.copy_failure,
+ Toast.LENGTH_SHORT).show();
+ break;
+ case MSG_CARD_NO_SPACE:
+ Toast.makeText(QuickContactActivity.this, R.string.card_no_space,
+ Toast.LENGTH_SHORT).show();
+ break;
+ case MSG_NO_EMPTY_EMAIL:
+ Toast.makeText(QuickContactActivity.this,
+ R.string.no_empty_email_in_usim,
+ Toast.LENGTH_SHORT).show();
+ break;
+ }
+ }
+ };
+ }
+
+ new Thread(new Runnable() {
+ public void run() {
+ synchronized (this) {
+ int adnCountInSimContact = 1;
+ int anrCountInSimContact = ContactUtils.getOneSimAnrCount
+ (QuickContactActivity.this, sub);
+ int emailCountInSimContact = ContactUtils.getOneSimEmailCount
+ (QuickContactActivity.this, sub);
+ int totalEmptyAdn = ContactUtils.getSimFreeCount(
+ QuickContactActivity.this, sub);
+ int totalEmptyAnr = ContactUtils.getSpareAnrCount(
+ QuickContactActivity.this, sub);
+ int totalEmptyEmail = ContactUtils.getSpareEmailCount(
+ QuickContactActivity.this, sub);
+
+ Message msg = Message.obtain();
+ if (totalEmptyAdn <= 0) {
+ msg.what = MSG_CARD_NO_SPACE;
+ mHandler.sendMessage(msg);
+ return;
+ }
+
+ //to indiacate how many number in one ADN can saved to SIM card,
+ //1 means can only save one number,2,3 ... means can save anr
+ int numEntitySize = adnCountInSimContact + anrCountInSimContact;
+
+ //empty number is equals to the sum of adn and anr
+ int emptyNumTotal = totalEmptyAdn + totalEmptyAnr;
+ int nameSource = mContactData.getDisplayNameSource();
+ String strName = "";
+ if (nameSource == DisplayNameSources.STRUCTURED_NAME
+ || nameSource == DisplayNameSources.NICKNAME
+ || nameSource == DisplayNameSources.STRUCTURED_PHONETIC_NAME)
+ strName = mContactData.getDisplayName();
+
+ ArrayList<String> arrayNumber = new ArrayList<String>();
+ ArrayList<String> arrayEmail = new ArrayList<String>();
+
+ for (RawContact rawContact : mContactData.getRawContacts()) {
+ for (DataItem dataItem : rawContact.getDataItems()) {
+ if (dataItem.getMimeType() == null) {
+ continue;
+ }
+ if (dataItem instanceof PhoneDataItem) {
+ // Get phone string
+ PhoneDataItem phoneNum = (PhoneDataItem) dataItem;
+ final String number = phoneNum.getNumber();
+ if (!TextUtils.isEmpty(number) && emptyNumTotal-- > 0) {
+ arrayNumber.add(number);
+ }
+ } else if (dataItem instanceof EmailDataItem) {
+ // Get email string
+ EmailDataItem emailData = (EmailDataItem) dataItem;
+ final String address = emailData.getData();
+ if (!TextUtils.isEmpty(address) && totalEmptyEmail-- > 0) {
+ arrayEmail.add(address);
+ }
+ }
+ }
+ }
+
+ //calculate how many ADN needed according to the number and email,
+ int groupNumCount = (arrayNumber.size() % numEntitySize) != 0 ? (arrayNumber
+ .size() / numEntitySize + 1) : (arrayNumber.size() / numEntitySize);
+ int groupEmailCount = emailCountInSimContact == 0 ? 0
+ : ((arrayEmail.size() % emailCountInSimContact) != 0 ? (arrayEmail
+ .size() / emailCountInSimContact + 1)
+ : (arrayEmail.size() / emailCountInSimContact));
+
+ int groupCount = Math.max(groupEmailCount, groupNumCount);
+
+ ArrayList<UsimEntity> results = new ArrayList<UsimEntity>();
+ for (int i = 0; i < groupCount; i++) {
+ results.add(new UsimEntity());
+ }
+
+ UsimEntity value;
+ //get the phone number for each ADN from arrayNumber,put them in UsimEntity
+ for (int i = 0; i < groupNumCount; i++) {
+ value = results.get(i);
+ ArrayList<String> numberItem = new ArrayList<String>();
+ for (int j = 0; j < numEntitySize; j++) {
+ if ((i * numEntitySize + j) < arrayNumber.size()) {
+ numberItem.add(arrayNumber.get(i * numEntitySize + j));
+ }
+ }
+ value.putNumberList(numberItem);
+ }
+
+ for (int i = 0; i < groupEmailCount; i++) {
+ value = results.get(i);
+ ArrayList<String> emailItem = new ArrayList<String>();
+ for (int j = 0; j < emailCountInSimContact; j++) {
+ if ((i * emailCountInSimContact + j) < arrayEmail.size()) {
+ emailItem.add(arrayEmail.get(i * emailCountInSimContact + j));
+ }
+ }
+ value.putEmailList(emailItem);
+ }
+
+ Uri itemUri = null;
+ if (totalEmptyEmail < 0 && ContactUtils.canSaveEmail(
+ QuickContactActivity.this, sub)) {
+ Message e_msg = Message.obtain();
+ e_msg.what = MSG_NO_EMPTY_EMAIL;
+ mHandler.sendMessage(e_msg);
+ }
+
+ //get phone number from UsimEntity,then insert to SIM card
+ for (int i = 0; i < groupCount; i++) {
+ value = results.get(i);
+ arrayNumber = (ArrayList<String>) value.getNumberList();
+ arrayEmail = (ArrayList<String>) value.getEmailList();
+ String strNum = arrayNumber.size() > 0 ? arrayNumber.get(0) : null;
+ StringBuilder strAnrNum = new StringBuilder();
+ for (int j = 1; j < arrayNumber.size(); j++) {
+ String s = arrayNumber.get(j);
+ strAnrNum.append(s);
+ strAnrNum.append(SimContactsConstants.ANR_SEP);
+ }
+ StringBuilder strEmail = new StringBuilder();
+ for (int j = 0; j < arrayEmail.size(); j++) {
+ String s = arrayEmail.get(j);
+ strEmail.append(s);
+ strEmail.append(SimContactsConstants.EMAIL_SEP);
+ }
+ itemUri = ContactUtils.insertToCard(QuickContactActivity.this, strName,
+ strNum, strEmail.toString(), strAnrNum.toString(), sub);
+ }
+ if (itemUri != null) {
+ msg.what = MSG_COPY_DONE;
+ mHandler.sendMessage(msg);
+ } else {
+ msg.what = MSG_COPY_FAILURE;
+ mHandler.sendMessage(msg);
+ }
+ }
+ }
+ }).start();
+ }
+
private boolean showRawContactPickerDialog() {
if (mContactData == null) return false;
startActivityForResult(EditorIntents.createViewLinkedContactsIntent(
diff --git a/src/com/android/contacts/util/AccountSelectionUtil.java b/src/com/android/contacts/util/AccountSelectionUtil.java
index bfe8a08..0a84f02 100644
--- a/src/com/android/contacts/util/AccountSelectionUtil.java
+++ b/src/com/android/contacts/util/AccountSelectionUtil.java
@@ -93,7 +93,7 @@
DialogInterface.OnCancelListener onCancelListener) {
final AccountTypeManager accountTypes = AccountTypeManager.getInstance(activity);
final List<AccountWithDataSet> writableAccountList =
- accountTypes.blockForWritableAccounts();
+ accountTypes.blockForWritableAccountsWithoutSim();
Log.i(LOG_TAG, "The number of available accounts: " + writableAccountList.size());
@@ -183,6 +183,8 @@
importIntent.putExtra("account_name", account.name);
importIntent.putExtra("account_type", account.type);
importIntent.putExtra("data_set", account.dataSet);
+ if (account.name == null && account.type == null)
+ importIntent.putExtra("local_account", true);
}
if (mVCardShare) {
diff --git a/src/com/android/contacts/util/ImageViewDrawableSetter.java b/src/com/android/contacts/util/ImageViewDrawableSetter.java
index b90cf1f..fa5b176 100644
--- a/src/com/android/contacts/util/ImageViewDrawableSetter.java
+++ b/src/com/android/contacts/util/ImageViewDrawableSetter.java
@@ -16,6 +16,7 @@
package com.android.contacts.util;
+import android.accounts.Account;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -30,7 +31,7 @@
import com.android.contacts.ContactPhotoManager.DefaultImageRequest;
import com.android.contacts.lettertiles.LetterTileDrawable;
import com.android.contacts.model.Contact;
-
+import com.android.contacts.model.RawContact;
import java.util.Arrays;
/**
@@ -53,9 +54,16 @@
}
public Bitmap setupContactPhoto(Contact contactData, ImageView photoView) {
+ Account account = null;
mContact = contactData;
setTarget(photoView);
- return setCompressedImage(contactData.getPhotoBinaryData());
+ RawContact rawContact = contactData.getRawContacts().get(0);
+ final String accountType = rawContact.getAccountTypeString();
+ final String accountName = rawContact.getAccountName();
+ if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
+ account = new Account(accountName, accountType);
+ }
+ return setCompressedImage(contactData.getPhotoBinaryData(), account);
}
public void setTransitionDuration(int durationInMillis) {
@@ -83,7 +91,7 @@
return mCompressed;
}
- protected Bitmap setCompressedImage(byte[] compressed) {
+ protected Bitmap setCompressedImage(byte[] compressed, Account account) {
if (mPreviousDrawable == null) {
// If we don't already have a drawable, skip the exit-early test
// below; otherwise we might not end up setting the default image.
@@ -99,7 +107,7 @@
Drawable newDrawable = decodedBitmapDrawable(compressed);
if (newDrawable == null) {
- newDrawable = defaultDrawable();
+ newDrawable = defaultDrawable(account);
}
// Remember this for next time, so that we can check if it changed.
@@ -140,7 +148,7 @@
* retrieve a default drawable for this contact. If not, then use the name as the contact
* identifier instead.
*/
- private Drawable defaultDrawable() {
+ private Drawable defaultDrawable(Account account) {
Resources resources = mTarget.getResources();
DefaultImageRequest request;
int contactType = ContactPhotoManager.TYPE_DEFAULT;
@@ -156,7 +164,8 @@
request = new DefaultImageRequest(mContact.getDisplayName(), mContact.getLookupKey(),
contactType, false /* isCircular */);
}
- return ContactPhotoManager.getDefaultAvatarDrawableForContact(resources, true, request);
+ return ContactPhotoManager.getDefaultAvatarDrawableForContact(
+ resources, true, request, account);
}
private BitmapDrawable decodedBitmapDrawable(byte[] compressed) {
diff --git a/src/com/android/contacts/vcard/ImportVCardActivity.java b/src/com/android/contacts/vcard/ImportVCardActivity.java
index 2c69cdf..95634c2 100644
--- a/src/com/android/contacts/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ImportVCardActivity.java
@@ -584,11 +584,14 @@
String accountName = null;
String accountType = null;
String dataSet = null;
+ boolean localAccount = false;
final Intent intent = getIntent();
if (intent != null) {
accountName = intent.getStringExtra(SelectAccountActivity.ACCOUNT_NAME);
accountType = intent.getStringExtra(SelectAccountActivity.ACCOUNT_TYPE);
dataSet = intent.getStringExtra(SelectAccountActivity.DATA_SET);
+ localAccount = intent.getBooleanExtra(
+ SelectAccountActivity.LOCAL_ACCOUNT, false);
} else {
Log.e(LOG_TAG, "intent does not exist");
}
@@ -596,16 +599,22 @@
if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
mAccount = new AccountWithDataSet(accountName, accountType, dataSet);
} else {
- final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
- final List<AccountWithDataSet> accountList = accountTypes.blockForWritableAccounts();
- if (accountList.size() == 0) {
- mAccount = null;
- } else if (accountList.size() == 1) {
- mAccount = accountList.get(0);
+ if (localAccount) {
+ mAccount = AccountWithDataSet.getNullAccount();
} else {
- startActivityForResult(new Intent(this, SelectAccountActivity.class),
- SELECT_ACCOUNT);
- return;
+ final AccountTypeManager accountTypes = AccountTypeManager
+ .getInstance(this);
+ final List<AccountWithDataSet> accountList = accountTypes
+ .blockForWritableAccounts();
+ if (accountList.size() == 0) {
+ mAccount = null;
+ } else if (accountList.size() == 1) {
+ mAccount = accountList.get(0);
+ } else {
+ startActivityForResult(new Intent(this,
+ SelectAccountActivity.class), SELECT_ACCOUNT);
+ return;
+ }
}
}
diff --git a/src/com/android/contacts/vcard/SelectAccountActivity.java b/src/com/android/contacts/vcard/SelectAccountActivity.java
index ac5b3eb..3dafe91 100644
--- a/src/com/android/contacts/vcard/SelectAccountActivity.java
+++ b/src/com/android/contacts/vcard/SelectAccountActivity.java
@@ -35,6 +35,7 @@
public static final String ACCOUNT_NAME = "account_name";
public static final String ACCOUNT_TYPE = "account_type";
public static final String DATA_SET = "data_set";
+ public static final String LOCAL_ACCOUNT = "local_account";
private class CancelListener
implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
@@ -58,7 +59,8 @@
// - no account -> use phone-local storage without asking the user
final int resId = R.string.import_from_vcf_file;
final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
- final List<AccountWithDataSet> accountList = accountTypes.blockForWritableAccounts();
+ final List<AccountWithDataSet> accountList = accountTypes
+ .blockForWritableAccountsWithoutSim();
if (accountList.size() == 0) {
Log.w(LOG_TAG, "Account does not exist");
finish();
diff --git a/tests/src/com/android/contacts/test/mocks/MockContactPhotoManager.java b/tests/src/com/android/contacts/test/mocks/MockContactPhotoManager.java
index e0bd425..4b6f6c9 100644
--- a/tests/src/com/android/contacts/test/mocks/MockContactPhotoManager.java
+++ b/tests/src/com/android/contacts/test/mocks/MockContactPhotoManager.java
@@ -16,6 +16,7 @@
package com.android.contacts.test.mocks;
+import android.accounts.Account;
import android.graphics.Bitmap;
import android.net.Uri;
import android.view.View;
@@ -29,14 +30,15 @@
*/
public class MockContactPhotoManager extends ContactPhotoManager {
@Override
- public void loadThumbnail(ImageView view, long photoId, boolean darkTheme, boolean isCircular,
- DefaultImageRequest defaultImageRequest, DefaultImageProvider defaultProvider) {
+ public void loadThumbnail(ImageView view, long photoId, Account account, boolean darkTheme,
+ boolean isCircular, DefaultImageRequest defaultImageRequest,
+ DefaultImageProvider defaultProvider) {
defaultProvider.applyDefaultImage(view, -1, darkTheme, null);
}
@Override
- public void loadPhoto(ImageView view, Uri photoUri, int requestedExtent, boolean darkTheme,
- boolean isCircular, DefaultImageRequest defaultImageRequest,
+ public void loadPhoto(ImageView view, Uri photoUri, Account account, int requestedExtent,
+ boolean darkTheme, boolean isCircular, DefaultImageRequest defaultImageRequest,
DefaultImageProvider defaultProvider) {
defaultProvider.applyDefaultImage(view, requestedExtent, darkTheme, null);
}