diff options
author | Xiaojing Zhang <zhangx@codeaurora.org> | 2015-10-20 17:40:59 +0800 |
---|---|---|
committer | Xiaojing Zhang <zhangx@codeaurora.org> | 2015-10-20 17:41:11 +0800 |
commit | faca3df8f57f70b676604ee7972ec2628b4796dd (patch) | |
tree | 92ca1a4f597e7a6a2a5a9ec7be6c6b6c983381f3 | |
parent | 470bea576a33b5a2e85e63f750bec0b62e2d2d55 (diff) | |
parent | ed28a1d8eccf352379768ae288c7fa4dda732d47 (diff) | |
download | android_packages_apps_ContactsCommon-faca3df8f57f70b676604ee7972ec2628b4796dd.tar.gz android_packages_apps_ContactsCommon-faca3df8f57f70b676604ee7972ec2628b4796dd.tar.bz2 android_packages_apps_ContactsCommon-faca3df8f57f70b676604ee7972ec2628b4796dd.zip |
Merge remote-tracking branch 'remotes/quic/ui_dev_2.0' into HEAD
Change-Id: Ibc079218a348574499304da141f64282b756ed17
85 files changed, 2985 insertions, 261 deletions
@@ -23,7 +23,7 @@ res_dirs := res $(phone_common_dir)/res LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs)) LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs)) - +LOCAL_JAVA_LIBRARIES := telephony-common LOCAL_AAPT_FLAGS := \ --auto-add-overlay \ --extra-packages com.android.phone.common diff --git a/TestCommon/src/com/android/contacts/common/test/mocks/MockAccountTypeManager.java b/TestCommon/src/com/android/contacts/common/test/mocks/MockAccountTypeManager.java index ab2d3954..d3dd722c 100644 --- a/TestCommon/src/com/android/contacts/common/test/mocks/MockAccountTypeManager.java +++ b/TestCommon/src/com/android/contacts/common/test/mocks/MockAccountTypeManager.java @@ -66,6 +66,11 @@ public class MockAccountTypeManager extends AccountTypeManager { } @Override + public List<AccountWithDataSet> getAccounts(boolean writableOnly, int flag) { + return Arrays.asList(mAccounts); + } + + @Override public List<AccountWithDataSet> getGroupWritableAccounts() { return Arrays.asList(mAccounts); } diff --git a/TestCommon/src/com/android/contacts/common/test/mocks/MockContactPhotoManager.java b/TestCommon/src/com/android/contacts/common/test/mocks/MockContactPhotoManager.java index db8f06f2..3b143deb 100644 --- a/TestCommon/src/com/android/contacts/common/test/mocks/MockContactPhotoManager.java +++ b/TestCommon/src/com/android/contacts/common/test/mocks/MockContactPhotoManager.java @@ -16,6 +16,7 @@ package com.android.contacts.common.test.mocks; +import android.accounts.Account; import android.graphics.Bitmap; import android.net.Uri; import android.view.View; @@ -29,16 +30,19 @@ import com.android.contacts.common.ContactPhotoManager; */ public class MockContactPhotoManager extends ContactPhotoManager { @Override - public void loadThumbnail(ImageView view, long photoId, boolean darkTheme, boolean isCircular, - DefaultImageRequest defaultImageRequest, DefaultImageProvider defaultProvider) { - defaultProvider.applyDefaultImage(view, -1, darkTheme, null); + public void loadThumbnail(ImageView view, long photoId, Account account, + boolean darkTheme, boolean isCircular, + DefaultImageRequest defaultImageRequest, + DefaultImageProvider defaultProvider) { + defaultProvider.applyDefaultImage(view, account, -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); + defaultProvider.applyDefaultImage(view, account, requestedExtent, darkTheme, null); } @Override @@ -69,4 +73,8 @@ public class MockContactPhotoManager extends ContactPhotoManager { @Override public void preloadPhotosInBackground() { } + + @Override + public void clear() { + } } diff --git a/res/drawable-hdpi/ic_contact_picture_sim.png b/res/drawable-hdpi/ic_contact_picture_sim.png Binary files differnew file mode 100755 index 00000000..7b6cc26f --- /dev/null +++ b/res/drawable-hdpi/ic_contact_picture_sim.png diff --git a/res/drawable-hdpi/ic_contact_picture_sim_1.png b/res/drawable-hdpi/ic_contact_picture_sim_1.png Binary files differnew file mode 100755 index 00000000..ec2ebcb5 --- /dev/null +++ b/res/drawable-hdpi/ic_contact_picture_sim_1.png diff --git a/res/drawable-hdpi/ic_contact_picture_sim_2.png b/res/drawable-hdpi/ic_contact_picture_sim_2.png Binary files differnew file mode 100755 index 00000000..3b68c682 --- /dev/null +++ b/res/drawable-hdpi/ic_contact_picture_sim_2.png diff --git a/res/drawable-hdpi/ic_contact_picture_sim_business.png b/res/drawable-hdpi/ic_contact_picture_sim_business.png Binary files differnew file mode 100755 index 00000000..4df1ecd5 --- /dev/null +++ b/res/drawable-hdpi/ic_contact_picture_sim_business.png diff --git a/res/drawable-hdpi/ic_contact_picture_sim_personal.png b/res/drawable-hdpi/ic_contact_picture_sim_personal.png Binary files differnew file mode 100755 index 00000000..44f4b930 --- /dev/null +++ b/res/drawable-hdpi/ic_contact_picture_sim_personal.png diff --git a/res/drawable-hdpi/ic_contact_picture_sim_primary.png b/res/drawable-hdpi/ic_contact_picture_sim_primary.png Binary files differnew file mode 100755 index 00000000..a5bf9214 --- /dev/null +++ b/res/drawable-hdpi/ic_contact_picture_sim_primary.png diff --git a/res/drawable-hdpi/ic_menu_settings_holo_light.png b/res/drawable-hdpi/ic_menu_settings_holo_light.png Binary files differindex b7bb5c41..ca012b85 100644..100755 --- a/res/drawable-hdpi/ic_menu_settings_holo_light.png +++ b/res/drawable-hdpi/ic_menu_settings_holo_light.png diff --git a/res/drawable-hdpi/phone_account.png b/res/drawable-hdpi/phone_account.png Binary files differnew file mode 100755 index 00000000..f2324885 --- /dev/null +++ b/res/drawable-hdpi/phone_account.png diff --git a/res/drawable-hdpi/sim1_account.png b/res/drawable-hdpi/sim1_account.png Binary files differnew file mode 100755 index 00000000..2d9a51c0 --- /dev/null +++ b/res/drawable-hdpi/sim1_account.png diff --git a/res/drawable-hdpi/sim2_account.png b/res/drawable-hdpi/sim2_account.png Binary files differnew file mode 100755 index 00000000..c512d524 --- /dev/null +++ b/res/drawable-hdpi/sim2_account.png diff --git a/res/drawable-hdpi/simcard_account.png b/res/drawable-hdpi/simcard_account.png Binary files differnew file mode 100755 index 00000000..0ee3dac2 --- /dev/null +++ b/res/drawable-hdpi/simcard_account.png diff --git a/res/drawable-mdpi/ic_contact_picture_sim.png b/res/drawable-mdpi/ic_contact_picture_sim.png Binary files differnew file mode 100755 index 00000000..dd63d11d --- /dev/null +++ b/res/drawable-mdpi/ic_contact_picture_sim.png diff --git a/res/drawable-mdpi/ic_contact_picture_sim_1.png b/res/drawable-mdpi/ic_contact_picture_sim_1.png Binary files differnew file mode 100755 index 00000000..4793a07e --- /dev/null +++ b/res/drawable-mdpi/ic_contact_picture_sim_1.png diff --git a/res/drawable-mdpi/ic_contact_picture_sim_2.png b/res/drawable-mdpi/ic_contact_picture_sim_2.png Binary files differnew file mode 100755 index 00000000..665a6714 --- /dev/null +++ b/res/drawable-mdpi/ic_contact_picture_sim_2.png diff --git a/res/drawable-mdpi/ic_contact_picture_sim_business.png b/res/drawable-mdpi/ic_contact_picture_sim_business.png Binary files differnew file mode 100755 index 00000000..c266e266 --- /dev/null +++ b/res/drawable-mdpi/ic_contact_picture_sim_business.png diff --git a/res/drawable-mdpi/ic_contact_picture_sim_personal.png b/res/drawable-mdpi/ic_contact_picture_sim_personal.png Binary files differnew file mode 100755 index 00000000..f1e75966 --- /dev/null +++ b/res/drawable-mdpi/ic_contact_picture_sim_personal.png diff --git a/res/drawable-mdpi/ic_contact_picture_sim_primary.png b/res/drawable-mdpi/ic_contact_picture_sim_primary.png Binary files differnew file mode 100755 index 00000000..493067a9 --- /dev/null +++ b/res/drawable-mdpi/ic_contact_picture_sim_primary.png diff --git a/res/drawable-mdpi/ic_menu_settings_holo_light.png b/res/drawable-mdpi/ic_menu_settings_holo_light.png Binary files differindex 1ebc112e..45a5ff0f 100644..100755 --- a/res/drawable-mdpi/ic_menu_settings_holo_light.png +++ b/res/drawable-mdpi/ic_menu_settings_holo_light.png diff --git a/res/drawable-mdpi/phone_account.png b/res/drawable-mdpi/phone_account.png Binary files differnew file mode 100755 index 00000000..1fa24f6c --- /dev/null +++ b/res/drawable-mdpi/phone_account.png diff --git a/res/drawable-mdpi/sim1_account.png b/res/drawable-mdpi/sim1_account.png Binary files differnew file mode 100755 index 00000000..cd80def2 --- /dev/null +++ b/res/drawable-mdpi/sim1_account.png diff --git a/res/drawable-mdpi/sim2_account.png b/res/drawable-mdpi/sim2_account.png Binary files differnew file mode 100755 index 00000000..fd52224a --- /dev/null +++ b/res/drawable-mdpi/sim2_account.png diff --git a/res/drawable-mdpi/simcard_account.png b/res/drawable-mdpi/simcard_account.png Binary files differnew file mode 100755 index 00000000..e5a76881 --- /dev/null +++ b/res/drawable-mdpi/simcard_account.png diff --git a/res/drawable-xhdpi/ic_contact_picture_sim.png b/res/drawable-xhdpi/ic_contact_picture_sim.png Binary files differnew file mode 100755 index 00000000..a39b7df0 --- /dev/null +++ b/res/drawable-xhdpi/ic_contact_picture_sim.png diff --git a/res/drawable-xhdpi/ic_contact_picture_sim_1.png b/res/drawable-xhdpi/ic_contact_picture_sim_1.png Binary files differnew file mode 100755 index 00000000..300c0288 --- /dev/null +++ b/res/drawable-xhdpi/ic_contact_picture_sim_1.png diff --git a/res/drawable-xhdpi/ic_contact_picture_sim_2.png b/res/drawable-xhdpi/ic_contact_picture_sim_2.png Binary files differnew file mode 100755 index 00000000..c7ce4223 --- /dev/null +++ b/res/drawable-xhdpi/ic_contact_picture_sim_2.png diff --git a/res/drawable-xhdpi/ic_contact_picture_sim_business.png b/res/drawable-xhdpi/ic_contact_picture_sim_business.png Binary files differnew file mode 100755 index 00000000..abf631f9 --- /dev/null +++ b/res/drawable-xhdpi/ic_contact_picture_sim_business.png diff --git a/res/drawable-xhdpi/ic_contact_picture_sim_personal.png b/res/drawable-xhdpi/ic_contact_picture_sim_personal.png Binary files differnew file mode 100755 index 00000000..e5f0cf0a --- /dev/null +++ b/res/drawable-xhdpi/ic_contact_picture_sim_personal.png diff --git a/res/drawable-xhdpi/ic_contact_picture_sim_primary.png b/res/drawable-xhdpi/ic_contact_picture_sim_primary.png Binary files differnew file mode 100755 index 00000000..543d6a7a --- /dev/null +++ b/res/drawable-xhdpi/ic_contact_picture_sim_primary.png diff --git a/res/drawable-xhdpi/ic_menu_settings_holo_light.png b/res/drawable-xhdpi/ic_menu_settings_holo_light.png Binary files differindex 68ba92bd..91d0444d 100644..100755 --- a/res/drawable-xhdpi/ic_menu_settings_holo_light.png +++ b/res/drawable-xhdpi/ic_menu_settings_holo_light.png diff --git a/res/drawable-xhdpi/phone_account.png b/res/drawable-xhdpi/phone_account.png Binary files differnew file mode 100755 index 00000000..a61f2034 --- /dev/null +++ b/res/drawable-xhdpi/phone_account.png diff --git a/res/drawable-xhdpi/sim1_account.png b/res/drawable-xhdpi/sim1_account.png Binary files differnew file mode 100755 index 00000000..c0ae46f2 --- /dev/null +++ b/res/drawable-xhdpi/sim1_account.png diff --git a/res/drawable-xhdpi/sim2_account.png b/res/drawable-xhdpi/sim2_account.png Binary files differnew file mode 100755 index 00000000..68fb25ce --- /dev/null +++ b/res/drawable-xhdpi/sim2_account.png diff --git a/res/drawable-xhdpi/simcard_account.png b/res/drawable-xhdpi/simcard_account.png Binary files differnew file mode 100755 index 00000000..92b382ee --- /dev/null +++ b/res/drawable-xhdpi/simcard_account.png diff --git a/res/drawable-xxhdpi/ic_menu_settings_holo_light.png b/res/drawable-xxhdpi/ic_menu_settings_holo_light.png Binary files differindex 5b672a3d..c87e0463 100644..100755 --- a/res/drawable-xxhdpi/ic_menu_settings_holo_light.png +++ b/res/drawable-xxhdpi/ic_menu_settings_holo_light.png diff --git a/res/drawable-xxhdpi/phone_account.png b/res/drawable-xxhdpi/phone_account.png Binary files differnew file mode 100755 index 00000000..04b06fda --- /dev/null +++ b/res/drawable-xxhdpi/phone_account.png diff --git a/res/drawable-xxhdpi/sim1_account.png b/res/drawable-xxhdpi/sim1_account.png Binary files differnew file mode 100755 index 00000000..9293bdbe --- /dev/null +++ b/res/drawable-xxhdpi/sim1_account.png diff --git a/res/drawable-xxhdpi/sim2_account.png b/res/drawable-xxhdpi/sim2_account.png Binary files differnew file mode 100755 index 00000000..4788f1cd --- /dev/null +++ b/res/drawable-xxhdpi/sim2_account.png diff --git a/res/drawable-xxhdpi/simcard_account.png b/res/drawable-xxhdpi/simcard_account.png Binary files differnew file mode 100755 index 00000000..e98fd1d9 --- /dev/null +++ b/res/drawable-xxhdpi/simcard_account.png diff --git a/res/drawable-xxxhdpi/ic_menu_settings_holo_light.png b/res/drawable-xxxhdpi/ic_menu_settings_holo_light.png Binary files differnew file mode 100755 index 00000000..dfdd5798 --- /dev/null +++ b/res/drawable-xxxhdpi/ic_menu_settings_holo_light.png diff --git a/res/drawable-xxxhdpi/phone_account.png b/res/drawable-xxxhdpi/phone_account.png Binary files differnew file mode 100755 index 00000000..3d726f60 --- /dev/null +++ b/res/drawable-xxxhdpi/phone_account.png diff --git a/res/drawable-xxxhdpi/sim1_account.png b/res/drawable-xxxhdpi/sim1_account.png Binary files differnew file mode 100755 index 00000000..389569ac --- /dev/null +++ b/res/drawable-xxxhdpi/sim1_account.png diff --git a/res/drawable-xxxhdpi/sim2_account.png b/res/drawable-xxxhdpi/sim2_account.png Binary files differnew file mode 100755 index 00000000..ce5d8ddb --- /dev/null +++ b/res/drawable-xxxhdpi/sim2_account.png diff --git a/res/drawable-xxxhdpi/simcard_account.png b/res/drawable-xxxhdpi/simcard_account.png Binary files differnew file mode 100755 index 00000000..5f18f34b --- /dev/null +++ b/res/drawable-xxxhdpi/simcard_account.png diff --git a/res/layout/ip_prefix_dialog.xml b/res/layout/ip_prefix_dialog.xml new file mode 100644 index 00000000..8a59b279 --- /dev/null +++ b/res/layout/ip_prefix_dialog.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (c) 2015, 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:orientation="vertical" > + + <EditText + android:id="@+id/ip_prefix_dialog_edit" + android:layout_width="match_parent" + android:layout_height="45dip" + android:layout_margin="10dip" + android:inputType="phone" + android:hint="@string/ipcall_dialog_edit_hint"/> + +</LinearLayout> diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index edd3f3ef..bc9d5383 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- +<!-- ~ Copyright (C) 2012 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); @@ -161,9 +161,11 @@ <string name="listCustomView" msgid="1915154113477432033">"自定义视图中的联系人"</string> <string name="listSingleContact" msgid="8525131203887307088">"单个联系人"</string> <string name="dialog_new_contact_account" msgid="4107520273478326011">"在以下帐户中创建联系人:"</string> + <string name="export_to_sim">"导出到 SIM 卡"</string> <string name="import_from_sim" msgid="4749894687871835873">"从 SIM 卡导入"</string> <string name="import_from_sim_summary" msgid="2306434118233541675">"从 SIM 卡“<xliff:g id="SIM_NAME">^1</xliff:g>” - <xliff:g id="SIM_NUMBER">^2</xliff:g> 导入"</string> <string name="import_from_sim_summary_no_number" msgid="8498561004799710237">"从 SIM 卡“<xliff:g id="SIM_NAME">%1$s</xliff:g>”导入"</string> + <string name="export_to_sim">"导出到 SIM 卡"</string> <string name="import_from_vcf_file" product="default" msgid="1381675161522633950">"从 .vcf 文件导入"</string> <string name="cancel_import_confirmation_message" msgid="7764915400478970495">"要取消导入“<xliff:g id="FILENAME">%s</xliff:g>”吗?"</string> <string name="cancel_export_confirmation_message" msgid="4063783315931861656">"要取消导出“<xliff:g id="FILENAME">%s</xliff:g>”吗?"</string> @@ -178,6 +180,7 @@ <string name="fail_reason_too_many_vcard" product="nosdcard" msgid="8720294715223591581">"存储设备中的 vCard 文件过多。"</string> <string name="fail_reason_too_many_vcard" product="default" msgid="3793454448838716962">"SD卡上的 vCard 文件过多。"</string> <string name="fail_reason_io_error" msgid="7736686553669161933">"I/O 错误"</string> + <string name="fail_reason_import_vcard">I/O 错误,无法导入 vCard <xliff:g id="name">%s</xliff:g></string> <string name="fail_reason_low_memory_during_import" msgid="3277485820827338116">"内存不足。该文件可能过大。"</string> <string name="fail_reason_vcard_parse_error" msgid="514012644716565082">"由于意外原因而无法解析 vCard。"</string> <string name="fail_reason_not_supported" msgid="388664373573337601">"不支持此格式。"</string> @@ -228,6 +231,7 @@ <string name="dialog_import_export" msgid="1125776851100740858">"导入/导出联系人"</string> <string name="dialog_import" msgid="5177004290082451296">"导入联系人"</string> <string name="share_error" msgid="665756457151793108">"无法分享此联系人。"</string> + <string name="share_failed">"联系人太多,分享失败。"</string> <string name="menu_search" msgid="7464453023659824700">"搜索"</string> <string name="menu_contacts_filter" msgid="586356478145511794">"要显示的联系人"</string> <string name="activity_title_contacts_filter" msgid="7689519428197855166">"要显示的联系人"</string> @@ -245,9 +249,38 @@ <string name="contact_status_update_attribution" msgid="8419168578670128134">"来源:<xliff:g id="SOURCE">%1$s</xliff:g>"</string> <string name="contact_status_update_attribution_with_date" msgid="7492465535645607473">"<xliff:g id="DATE">%1$s</xliff:g>,来源:<xliff:g id="SOURCE">%2$s</xliff:g>"</string> <string name="action_menu_back_from_search" msgid="1138551123844019647">"停止搜索"</string> + <string name="exporting">"正在导出"</string> + <string name="reading_vcard_files">读取VCARD文件</string> + <string name="progressdialog_cancel">取消</string> + <string name="export_to_sim_failed">"导出失败,<xliff:g id="insertCount">%d</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="tag_too_long">"发生错误,联系人姓名过长。"</string> + <string name="sim_contacts_not_load">发生错误,SIM卡联系人未完全加载</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="import_from_sim_select">"选择SIM卡进行导入"</string> + <string name="no_sdcard_title" product="nosdcard">"存储设备不存在"</string> + <string name="no_sdcard_title" product="default">"无 SD 卡"</string> + <string name="deleteConfirmation_title">"要删除联系人吗?"</string> + <string name="deleteConfirmation">"将会删除此联系人。"</string> + <string name="slot_name">"卡"</string> + <string name="copy_done">复制成功!</string> + <string name="copy_failure">复制失败!</string> + <string name="card_no_space">卡记录已满,部分信息未复制</string> + <string name="select_path">请选择路径</string> + <string name="sd_card">SD 卡</string> + <string name="phone_storage">手机内存</string> <string name="description_clear_search" msgid="3893511425518852086">"清除搜索内容"</string> <string name="settings_contact_display_options_title" msgid="1020420603072835628">"联系人显示选项"</string> <string name="select_account_dialog_title" msgid="5509088895267310568">"帐户"</string> <string name="set_default_account" msgid="3865970860434642695">"一律使用这张卡进行通话"</string> <string name="select_phone_account_for_calls" msgid="933905607702811164">"用于外拨电话的帐户"</string> + <!-- Menu item used to initiate ip call --> + <string name="ip_call_by_slot">IP拨号通过<xliff:g id="subName">%s</xliff:g></string> + <string name="set_ip_number">设置IP号</string> + <string name="no_ip_number">没有IP号</string> + <string name="no_ip_number_on_sim_card">SIM卡上没有IP号</string> + <string name="ipcall_dialog_title">IP电话设置</string> + <string name="ipcall_dialog_edit_hint">请输入IP电话前缀</string> </resources> diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index 3dc3cfd1..3e5ef1ab 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -161,6 +161,7 @@ <string name="listCustomView" msgid="1915154113477432033">"聯絡人自訂檢視"</string> <string name="listSingleContact" msgid="8525131203887307088">"單一聯絡人"</string> <string name="dialog_new_contact_account" msgid="4107520273478326011">"在帳戶下建立聯絡人"</string> + <string name="export_to_sim">"導出到 SIM 卡"</string> <string name="import_from_sim" msgid="4749894687871835873">"從 SIM 卡匯入"</string> <string name="import_from_sim_summary" msgid="2306434118233541675">"從 SIM 卡 <xliff:g id="SIM_NAME">^1</xliff:g> - <xliff:g id="SIM_NUMBER">^2</xliff:g> 匯入"</string> <string name="import_from_sim_summary_no_number" msgid="8498561004799710237">"從 SIM 卡 <xliff:g id="SIM_NAME">%1$s</xliff:g> 匯入"</string> @@ -178,6 +179,7 @@ <string name="fail_reason_too_many_vcard" product="nosdcard" msgid="8720294715223591581">"儲存裝置中的 vCard 檔案過多。"</string> <string name="fail_reason_too_many_vcard" product="default" msgid="3793454448838716962">"SD 卡上的 vCard 檔案過多。"</string> <string name="fail_reason_io_error" msgid="7736686553669161933">"I/O 錯誤"</string> + <string name="fail_reason_import_vcard">I/O 錯誤,無法匯入vCard <xliff:g id="name">%s</xliff:g></string> <string name="fail_reason_low_memory_during_import" msgid="3277485820827338116">"記憶體不足,檔案可能過大。"</string> <string name="fail_reason_vcard_parse_error" msgid="514012644716565082">"由於意外因素,導致無法剖析 vCard。"</string> <string name="fail_reason_not_supported" msgid="388664373573337601">"不支援此格式。"</string> @@ -247,6 +249,24 @@ <string name="action_menu_back_from_search" msgid="1138551123844019647">"停止搜尋"</string> <string name="description_clear_search" msgid="3893511425518852086">"清除搜尋"</string> <string name="settings_contact_display_options_title" msgid="1020420603072835628">"聯絡人顯示選項"</string> + <string name="settings_contact_display_options_description" msgid="4130259058302284077">"設定聯絡人的顯示和排序方式。"</string> + <string name="exporting">"正在導出"</string> + <string name="reading_vcard_files">讀取Vcard文件</string> + <string name="export_to_sim_failed">"導出失敗,<xliff:g id="insertCount">%d</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_no_phone_or_email">導出失敗, <xliff:g id="name">%s</xliff:g>沒有電話號碼或者郵箱地址</string> + <string name="tag_too_long">"發生錯誤,聯絡人姓名過長。"</string> + <string name="import_from_sim_select">"選擇SIM卡進行導入"</string> + <string name="no_sdcard_title" product="nosdcard">"無存儲設備"</string> + <string name="no_sdcard_title" product="default">"無 SD 卡"</string> + <string name="deleteConfirmation_title">"要刪除聯絡人嗎?"</string> + <string name="ContactMultiDeleteConfirmation">"將會刪除這些聯絡人。"</string> + <string name="slot_name">"卡"</string> + + <string name="copy_done">複制成功!</string> + <string name="copy_failure">複制失敗!</string> + <string name="card_no_space">卡記錄已滿,部分信息未複制</string> <string name="select_account_dialog_title" msgid="5509088895267310568">"帳戶"</string> <string name="set_default_account" msgid="3865970860434642695">"一律使用這張 SIM 卡通話"</string> <string name="select_phone_account_for_calls" msgid="933905607702811164">"選擇通話帳戶"</string> diff --git a/res/values/strings.xml b/res/values/strings.xml index 2c093e7a..dd5cefbf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -425,6 +425,12 @@ a ren't members of any other group. [CHAR LIMIT=25] --> <!-- Action that exports all contacts to SIM --> <string name="export_to_sim">Export to SIM card</string> + <!-- Action string for selecting SIM for importing contacts --> + <string name="import_from_sim">Import from SIM card</string> + + <!-- Action string for selecting (USB) storage for importing contacts [CHAR LIMIT=30] --> + <string name="import_from_sdcard" product="default">Import from storage</string> + <!-- Action string for selecting a SIM subscription for importing contacts --> <string name="import_from_sim_summary">Import from SIM <xliff:g id="sim_name">^1</xliff:g> - <xliff:g id="sim_number">^2</xliff:g></string> @@ -493,6 +499,9 @@ a ren't members of any other group. [CHAR LIMIT=25] --> emitted some I/O error. Exact reason will be appended by the system. [CHAR LIMIT=NONE] --> <string name="fail_reason_io_error">I/O error</string> + <!--The failed reason shown when importting some a vCard file --> + <string name="fail_reason_import_vcard">I/O error,couldn\'t import vCard <xliff:g id="name">%s</xliff:g></string> + <!-- Failure reason show when Contacts app (especially vCard importer) encountered low memory problem and could not proceed its import procedure. [CHAR LIMIT=NONE] --> <string name="fail_reason_low_memory_during_import">Not enough memory. The file may be too large.</string> @@ -645,6 +654,9 @@ a ren't members of any other group. [CHAR LIMIT=25] --> <!-- Action that exports all contacts to a user selected destination. [CHAR LIMIT=25] --> <string name="export_to_vcf_file" product="default">Export to .vcf file</string> + <!-- Action that exports all contacts to (USB) storage [CHAR LIMIT=25] --> + <string name="export_to_sdcard" product="default">Export to storage</string> + <!-- Contact preferences related strings --> <!-- Label of the "sort by" display option --> @@ -680,6 +692,8 @@ a ren't members of any other group. [CHAR LIMIT=25] --> <!-- Toast indicating that sharing a contact has failed. [CHAR LIMIT=NONE] --> <string name="share_error">This contact can\'t be shared.</string> + <!-- Toast indicating that sharing too many contact has failed. [CHAR LIMIT=NONE] --> + <string name="share_failed">Too many contacts, share failed</string> <!-- Menu item to search contacts --> <string name="menu_search">Search</string> @@ -737,6 +751,52 @@ a ren't members of any other group. [CHAR LIMIT=25] --> <!-- Attribution of a contact status update, when the time of update is known --> <string name="contact_status_update_attribution_with_date"><xliff:g id="date" example="3 hours ago">%1$s</xliff:g> via <xliff:g id="source" example="Google Talk">%2$s</xliff:g></string> + <string name="label_groups">Group</string> + + <string name="exporting">Exporting</string> + + <!-- Message while reading multiple vCard files "(current number) of (total number) files" + The order of "current number" and "total number" cannot be changed --> + <string name="reading_vcard_files"><xliff:g id="current_number">%1$s</xliff:g> of <xliff:g id="total_number">%2$s</xliff:g> files</string> + + <string name="progressdialog_cancel">Cancel</string> + + <string name="export_to_sim_failed">Export failed, <xliff:g id="insertCount">%d</xliff:g> items are exported</string> + + <string name="export_sim_card_full">Sim Card is full, <xliff:g id="insertCount">%d</xliff:g> items are exported</string> + + <string name="export_failed">Export failed</string> + + <string name="sim_card_full">Error, Sim Card is full.</string> + + <string name="export_finished">Export finished</string> + + <string name="tag_too_long">Error, Contact name is too long.</string> + + <string name="sim_contacts_not_load">Error, SIM contacts have not been loaded completely.</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="import_from_sim_select">Choose card to import</string> + + <!-- Dialog title shown when (USB) storage does not exist [CHAR LIMIT=25] --> + <string name="no_sdcard_title" product="nosdcard">Storage unavailable</string> + <!-- Dialog title shown when SD Card does not exist --> + <string name="no_sdcard_title" product="default">No SD card</string> + <!-- Confirmation dialog title after users selects to delete a contact. [CHAR LIMIT=25]--> + <string name="deleteConfirmation_title">Delete contact?</string> + + <!-- Confirmation dialog contents after users selects to delete a Writable contact. --> + <string name="deleteConfirmation">This contact will be deleted.</string> + <!-- The slot common name --> + <string name="slot_name">SIM</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> <!-- Font family used when drawing letters for letter tile avatars. Do not translate. --> <string name="letter_tile_letter_font_family">sans-serif-light</string> @@ -764,4 +824,21 @@ a ren't members of any other group. [CHAR LIMIT=25] --> <!-- Title for dialog to select Phone Account for outgoing call. [CHAR LIMIT=40] --> <string name="select_phone_account_for_calls">Call with</string> + <!-- Menu item used to initiate ip call --> + <string name="ip_call_by_slot">IP Call by <xliff:g id="subName">%s</xliff:g></string> + <string name="set_ip_number">Set IP Number</string> + <string name="no_ip_number">No IP number</string> + <string name="no_ip_number_on_sim_card">No IP number on SIM card</string> + <string name="ipcall_dialog_title">IP call setting</string> + <string name="ipcall_dialog_edit_hint">Please input the IP prefix</string> + + <string name="Import_All">Import Contacts From All SIMs</string> + <string name="select_path">Select Path</string> + <string name="sd_card">SD card</string> + <string name="phone_storage">Phone storage</string> + <string name="select_sim">Select SIM</string> + + <string name="import_contacts_sim">Import contacts from SIM?</string> + <string name="import_contacts_sim_confirm">Import</string> + <string name="import_contacts_sim_cancel">Cancel</string> </resources> diff --git a/src/com/android/contacts/common/CallUtil.java b/src/com/android/contacts/common/CallUtil.java index 1c62194b..7d131946 100644 --- a/src/com/android/contacts/common/CallUtil.java +++ b/src/com/android/contacts/common/CallUtil.java @@ -48,7 +48,7 @@ public class CallUtil { * automatically. */ public static Intent getCallIntent(String number) { - return getCallIntent(getCallUri(number)); + return getCallIntent(number, null); } /** @@ -60,6 +60,28 @@ public class CallUtil { } /** + * A variant of {@link #getCallIntent(String, String)} but also include {@code Account}. + */ + public static Intent getCallIntent( + String number, PhoneAccountHandle accountHandle) { + return getCallIntent(CallUtil.getCallUri(number), accountHandle); + } + + /** + * A variant of {@link #getCallIntent(android.net.Uri)} but also accept a call + * origin and {@code Account} and {@code VideoCallProfile} state. + * For more information about call origin, see comments in Phone package (PhoneApp). + */ + public static Intent getCallIntent(Uri uri, PhoneAccountHandle accountHandle) { + final Intent intent = new Intent(Intent.ACTION_CALL, uri); + if (accountHandle != null) { + intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle); + } + + return intent; + } + + /** * A variant of {@link #getCallIntent} for starting a video call. */ public static Intent getVideoCallIntent(String number, String callOrigin) { diff --git a/src/com/android/contacts/common/ContactPhotoManager.java b/src/com/android/contacts/common/ContactPhotoManager.java index deaf40e0..c4cde8bb 100644 --- a/src/com/android/contacts/common/ContactPhotoManager.java +++ b/src/com/android/contacts/common/ContactPhotoManager.java @@ -16,6 +16,7 @@ package com.android.contacts.common; +import android.accounts.Account; import android.app.ActivityManager; import android.content.ComponentCallbacks2; import android.content.ContentResolver; @@ -122,17 +123,23 @@ public abstract class ContactPhotoManager implements ComponentCallbacks2 { */ public static Drawable getDefaultAvatarDrawableForContact(Resources resources, boolean hires, DefaultImageRequest defaultImageRequest) { + return getDefaultAvatarDrawableForContact(null, resources, hires, + defaultImageRequest, null); + } + + public static Drawable getDefaultAvatarDrawableForContact(Context context, 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); + context, resources, null, account); } return sDefaultLetterAvatar; } - return LetterTileDefaultImageProvider.getDefaultImageForContact(resources, - defaultImageRequest); + return LetterTileDefaultImageProvider.getDefaultImageForContact(context, resources, + defaultImageRequest, account); } /** @@ -374,6 +381,10 @@ public abstract class ContactPhotoManager implements ComponentCallbacks2 { */ 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) { + } } /** @@ -385,14 +396,21 @@ public abstract class ContactPhotoManager implements ComponentCallbacks2 { @Override public void applyDefaultImage(ImageView view, int extent, boolean darkTheme, DefaultImageRequest defaultImageRequest) { - final Drawable drawable = getDefaultImageForContact(view.getResources(), - 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.getContext(), + view.getResources(), defaultImageRequest, account); view.setImageDrawable(drawable); } - public static Drawable getDefaultImageForContact(Resources resources, - DefaultImageRequest defaultImageRequest) { - final LetterTileDrawable drawable = new LetterTileDrawable(resources); + public static Drawable getDefaultImageForContact(Context context, Resources resources, + DefaultImageRequest defaultImageRequest, Account account) { + final LetterTileDrawable drawable = new LetterTileDrawable( + context, 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 @@ -448,6 +466,8 @@ public abstract class ContactPhotoManager implements ComponentCallbacks2 { return new ContactPhotoManagerImpl(context); } + public abstract void clear(); + @VisibleForTesting public static void injectContactPhotoManagerForTesting(ContactPhotoManager photoManager) { sInstance = photoManager; @@ -458,19 +478,32 @@ public abstract class ContactPhotoManager implements ComponentCallbacks2 { * 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); /** * Calls {@link #loadThumbnail(ImageView, long, boolean, DefaultImageRequest, * DefaultImageProvider)} using the {@link DefaultImageProvider} {@link #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) { - 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, 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, @@ -489,8 +522,9 @@ public abstract class ContactPhotoManager implements ComponentCallbacks2 { * @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, + public abstract void loadPhoto(ImageView view, Uri photoUri, + Account account, int requestedExtent, boolean darkTheme, + boolean isCircular, DefaultImageRequest defaultImageRequest, DefaultImageProvider defaultProvider); /** @@ -501,12 +535,25 @@ public abstract class ContactPhotoManager implements ComponentCallbacks2 { * @param defaultImageRequest {@link DefaultImageRequest} object that specifies how a default * letter tile avatar should be drawn. */ + 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) { - loadPhoto(view, photoUri, requestedExtent, darkTheme, isCircular, + loadPhoto(view, photoUri, null, 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 @@ -515,9 +562,17 @@ public abstract class ContactPhotoManager implements ComponentCallbacks2 { * @param defaultImageRequest {@link DefaultImageRequest} object that specifies how a default * letter tile avatar should be drawn. */ + 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); + } + 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); } /** @@ -819,11 +874,12 @@ class ContactPhotoManagerImpl extends ContactPhotoManager implements Callback { } @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); @@ -833,19 +889,19 @@ class ContactPhotoManagerImpl extends ContactPhotoManager implements Callback { } @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, account, photoUri, requestedExtent, + darkTheme, isCircular, defaultProvider); } else { loadPhotoByIdOrUri(view, Request.createFromUri(photoUri, requestedExtent, darkTheme, isCircular, defaultProvider)); @@ -853,11 +909,12 @@ class ContactPhotoManagerImpl extends ContactPhotoManager implements Callback { } } - private void createAndApplyDefaultImageForUri(ImageView view, Uri uri, int requestedExtent, - boolean darkTheme, boolean isCircular, DefaultImageProvider defaultProvider) { + private void createAndApplyDefaultImageForUri(ImageView view, + Account account, Uri uri, 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/common/ContactTileLoaderFactory.java b/src/com/android/contacts/common/ContactTileLoaderFactory.java index f8b0c359..33d4ea68 100644 --- a/src/com/android/contacts/common/ContactTileLoaderFactory.java +++ b/src/com/android/contacts/common/ContactTileLoaderFactory.java @@ -23,6 +23,8 @@ import android.net.Uri; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.RawContacts; +import android.text.TextUtils; /** * Used to create {@link CursorLoader}s to load different groups of @@ -35,19 +37,22 @@ public final class ContactTileLoaderFactory { public final static int STARRED = 2; public final static int PHOTO_URI = 3; public final static int LOOKUP_KEY = 4; - public final static int CONTACT_PRESENCE = 5; - public final static int CONTACT_STATUS = 6; + public final static int ACCOUNT_TYPE = 5; + public final static int ACCOUNT_NAME = 6; + + public final static int CONTACT_PRESENCE = 7; + public final static int CONTACT_STATUS = 8; // Only used for StrequentPhoneOnlyLoader - public final static int PHONE_NUMBER = 5; - public final static int PHONE_NUMBER_TYPE = 6; - public final static int PHONE_NUMBER_LABEL = 7; - public final static int IS_DEFAULT_NUMBER = 8; - public final static int PINNED = 9; + public final static int PHONE_NUMBER = 7; + public final static int PHONE_NUMBER_TYPE = 8; + public final static int PHONE_NUMBER_LABEL = 9; + public final static int IS_DEFAULT_NUMBER = 10; + public final static int PINNED = 11; // The _ID field returned for strequent items actually contains data._id instead of // contacts._id because the query is performed on the data table. In order to obtain the // contact id for strequent items, we thus have to use Phone.contact_id instead. - public final static int CONTACT_ID_FOR_DATA = 10; + public final static int CONTACT_ID_FOR_DATA = 12; private static final String[] COLUMNS = new String[] { Contacts._ID, // ..........................................0 @@ -55,8 +60,10 @@ public final class ContactTileLoaderFactory { Contacts.STARRED, // ......................................2 Contacts.PHOTO_URI, // ....................................3 Contacts.LOOKUP_KEY, // ...................................4 - Contacts.CONTACT_PRESENCE, // .............................5 - Contacts.CONTACT_STATUS, // ...............................6 + RawContacts.ACCOUNT_TYPE, // 5 + RawContacts.ACCOUNT_NAME, // 6 + Contacts.CONTACT_PRESENCE, // .............................7 + Contacts.CONTACT_STATUS, // ...............................8 }; /** @@ -72,12 +79,14 @@ public final class ContactTileLoaderFactory { Contacts.STARRED, // ......................................2 Contacts.PHOTO_URI, // ....................................3 Contacts.LOOKUP_KEY, // ...................................4 - Phone.NUMBER, // ..........................................5 - Phone.TYPE, // ............................................6 - Phone.LABEL, // ...........................................7 - Phone.IS_SUPER_PRIMARY, //.................................8 - Contacts.PINNED, // .......................................9 - Phone.CONTACT_ID //........................................10 + RawContacts.ACCOUNT_TYPE, // 5 + RawContacts.ACCOUNT_NAME, // 6 + Phone.NUMBER, // ..........................................7 + Phone.TYPE, // ............................................8 + Phone.LABEL, // ............................................9 + Phone.IS_SUPER_PRIMARY, //.................................10 + Contacts.PINNED, // .......................................11 + Phone.CONTACT_ID //........................................12 }; private static final String STARRED_ORDER = Contacts.DISPLAY_NAME+" COLLATE NOCASE ASC"; @@ -88,10 +97,16 @@ public final class ContactTileLoaderFactory { } public static CursorLoader createStrequentPhoneOnlyLoader(Context context) { - Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon() - .appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build(); - - return new CursorLoader(context, uri, COLUMNS_PHONE_ONLY, null, null, null); + Uri.Builder builder = Contacts.CONTENT_STREQUENT_URI.buildUpon(); + builder.appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true"); + // Do not show contacts in disabled SIM card + String disabledSimFilter = MoreContactUtils.getDisabledSimFilter(); + if (!TextUtils.isEmpty(disabledSimFilter)) { + builder.appendQueryParameter(RawContacts.ACCOUNT_NAME, disabledSimFilter); + builder.appendQueryParameter(SimContactsConstants + .WITHOUT_SIM_FLAG, "true"); + } + return new CursorLoader(context, builder.build(), COLUMNS_PHONE_ONLY, null, null, null); } public static CursorLoader createStarredLoader(Context context) { diff --git a/src/com/android/contacts/common/MoreContactUtils.java b/src/com/android/contacts/common/MoreContactUtils.java index 9b9f800e..8f465ad4 100644 --- a/src/com/android/contacts/common/MoreContactUtils.java +++ b/src/com/android/contacts/common/MoreContactUtils.java @@ -1,6 +1,10 @@ /* + * Copyright (C) 2013-2015, The Linux Foundation. All Rights Reserved. + * Not a Contribution. + * * Copyright (C) 2012 The Android Open Source Project * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -16,20 +20,55 @@ package com.android.contacts.common; +import android.accounts.Account; + import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberUtil; +import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.ComponentName; +import android.content.DialogInterface; import android.content.Intent; +import android.content.OperationApplicationException; +import android.database.Cursor; import android.graphics.Rect; import android.net.Uri; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; 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.provider.Settings; +import android.telephony.SubscriptionManager; +import android.telecom.PhoneAccountHandle; +import android.telephony.TelephonyManager; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; +import android.view.LayoutInflater; +import android.util.Log; import android.view.View; +import android.widget.EditText; import android.widget.TextView; +import com.android.internal.telephony.PhoneConstants; import com.android.contacts.common.model.account.AccountType; +import com.android.contacts.common.model.account.SimAccountType; +import com.android.internal.telephony.uicc.AdnRecord; +import com.android.internal.telephony.uicc.IccConstants; +import com.android.internal.telephony.IIccPhoneBook; + +import java.util.ArrayList; +import java.util.List; /** * Shared static contact utility methods. @@ -37,7 +76,30 @@ import com.android.contacts.common.model.account.AccountType; public class MoreContactUtils { private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT); + private static final boolean DBG = true; + private static final String TAG = "MoreContactUtils"; + public static final int MAX_LENGTH_NAME_IN_SIM = 14; + public static final int MAX_LENGTH_NAME_WITH_CHINESE_IN_SIM = 6; + public static final int MAX_LENGTH_NUMBER_IN_SIM = 20; + public static final int MAX_LENGTH_EMAIL_IN_SIM = 40; + 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 String PHONEBOOK = "simphonebook"; + public static final String[] MULTI_SIM_NAME = { "perferred_name_sub1", + "perferred_name_sub2" }; + public static final String PREFERRED_SIM_ICON_INDEX = "preferred_sim_icon_index"; + public static final String[] IPCALL_PREFIX = { "ipcall_prefix1", + "ipcall_prefix2" }; + public final static int[] IC_SIM_PICTURE = { + R.drawable.ic_contact_picture_sim_1, + R.drawable.ic_contact_picture_sim_2, + R.drawable.ic_contact_picture_sim_personal, + R.drawable.ic_contact_picture_sim_business, + R.drawable.ic_contact_picture_sim_primary + }; /** * Returns true if two data with mimetypes which represent values in contact entries are * considered equal for collapsing in the GUI. For caller-id, use @@ -240,4 +302,540 @@ public class MoreContactUtils { intent.setData(lookupUri); return intent; } + + /** get disabled SIM card's name */ + public static String getDisabledSimFilter() { + int count = TelephonyManager.getDefault().getPhoneCount(); + StringBuilder simFilter = new StringBuilder(""); + + for (int i = 0; i < count; i++) { + if (TelephonyManager.SIM_STATE_UNKNOWN == TelephonyManager + .getDefault().getSimState(i)) { + simFilter.append(getSimAccountName(i) + ','); + } + } + + return simFilter.toString(); + } + + public static boolean isAPMOnAndSIMPowerDown(Context context) { + if (context == null) { + return false; + } + boolean isAirPlaneMode = Settings.System.getInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) == 1; + boolean isSIMPowerDown = SystemProperties.getInt( + "persist.radio.apm_sim_not_pwdn", 0) == 0; + return isAirPlaneMode && isSIMPowerDown; + } + + /** + * Get SIM card account name + */ + public static String getSimAccountName(int subscription) { + if (TelephonyManager.getDefault().isMultiSimEnabled()) { + return SimContactsConstants.SIM_NAME + (subscription + 1); + } else { + return SimContactsConstants.SIM_NAME; + } + } + + public static int getSubscription(String accountType, String accountName) { + int subscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + if (accountType == null || accountName == null) + return subscription; + if (accountType.equals(SimContactsConstants.ACCOUNT_TYPE_SIM)) { + if (accountName.equals(SimContactsConstants.SIM_NAME) + || accountName.equals(SimContactsConstants.SIM_NAME_1)) { + subscription = PhoneConstants.SUB1; + } else if (accountName.equals(SimContactsConstants.SIM_NAME_2)) { + subscription = PhoneConstants.SUB2; + } + } + return subscription; + } + + public static int getAnrCount(int slot) { + int anrCount = 0; + int[] subId = SubscriptionManager.getSubId(slot); + try { + IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface( + ServiceManager.getService("simphonebook")); + + if (iccIpb != null) { + if (subId != null + && TelephonyManager.getDefault().isMultiSimEnabled()) { + anrCount = iccIpb.getAnrCountUsingSubId(subId[0]); + } else { + anrCount = iccIpb.getAnrCount(); + } + } + } catch (RemoteException ex) { + // ignore it + } + + return anrCount; + } + + public static int getAdnCount(int slot) { + int adnCount = 0; + int[] subId = SubscriptionManager.getSubId(slot); + try { + IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface( + ServiceManager.getService("simphonebook")); + if (iccIpb != null) { + if (subId != null + && TelephonyManager.getDefault().isMultiSimEnabled()) { + adnCount = iccIpb.getAdnCountUsingSubId(subId[0]); + } else { + adnCount = iccIpb.getAdnCount(); + } + } + } catch (RemoteException ex) { + // ignore it + } + return adnCount; + } + + public static int getEmailCount(int slot) { + int emailCount = 0; + int[] subId = SubscriptionManager.getSubId(slot); + try { + IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface( + ServiceManager.getService("simphonebook")); + + if (iccIpb != null) { + if (subId != null + && TelephonyManager.getDefault().isMultiSimEnabled()) { + emailCount = iccIpb.getEmailCountUsingSubId(subId[0]); + } else { + emailCount = iccIpb.getEmailCount(); + } + } + } catch (RemoteException ex) { + // ignore it + } + + return emailCount; + } + + /** + * Returns the subscription's card can save anr or not. + */ + public static boolean canSaveAnr(int slot) { + return getAnrCount(slot) > 0 ? true : false; + } + + /** + * Returns the subscription's card can save email or not. + */ + public static boolean canSaveEmail(int subscription) { + return getEmailCount(subscription) > 0 ? true : false; + } + + public static int getOneSimAnrCount(int sub) { + int count = 0; + int anrCount = getAnrCount(sub); + int adnCount = getAdnCount(sub); + if (adnCount > 0) { + count = anrCount % adnCount != 0 ? (anrCount / adnCount + 1) + : (anrCount / adnCount); + } + return count; + } + + public static int getOneSimEmailCount(int sub) { + int count = 0; + int emailCount = getEmailCount(sub); + int adnCount = getAdnCount(sub); + if (adnCount > 0) { + count = emailCount % adnCount != 0 ? (emailCount / adnCount + 1) + : (emailCount / adnCount); + } + return count; + } + + public static boolean insertToPhone(String[] values, final ContentResolver resolver,int sub) { + Account account = getAcount(sub); + 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; + } + if (DBG) { + 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.DISPLAY_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.TYPE, Email.TYPE_MOBILE); + builder.withValue(Email.ADDRESS, emailAddress); + operationList.add(builder.build()); + } + } + } + + try { + ContentProviderResult[] results = + resolver.applyBatch(ContactsContract.AUTHORITY, operationList); + for (ContentProviderResult result: results) { + if (result.uri == null) { + success = false; + break; + } + } + return success; + } catch (RemoteException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + return false; + } catch (OperationApplicationException 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 subscription) { + // add the max count limit of Chinese code or not + if (!TextUtils.isEmpty(name)) { + final int maxLen = hasChinese(name) ? MAX_LENGTH_NAME_WITH_CHINESE_IN_SIM + : MAX_LENGTH_NAME_IN_SIM; + if (name.length() > maxLen) { + name = name.substring(0, maxLen); + } + } + Uri result; + ContentValues mValues = new ContentValues(); + mValues.clear(); + mValues.put(SimContactsConstants.STR_TAG, name); + if (!TextUtils.isEmpty(number)) { + number = PhoneNumberUtils.stripSeparators(number); + if (number.length() > MAX_LENGTH_NUMBER_IN_SIM) { + number = number.substring(0, MAX_LENGTH_NUMBER_IN_SIM); + } + + 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, subscription); + + if (result != null) { + // we should import the contact to the sim account at the same time. + String[] value = new String[] { + name, number, emails, anrNumber + }; + insertToPhone(value, context.getContentResolver(),subscription); + } else { + Log.e(TAG, "export contact: [" + name + ", " + number + ", " + emails + "] to slot " + + subscription + " failed"); + } + return result; + } + + public static Account getAcount(int sub) { + Account account = null; + if (TelephonyManager.getDefault().isMultiSimEnabled()) { + if (sub == PhoneConstants.SUB1) { + account = new Account(SimContactsConstants.SIM_NAME_1, + SimContactsConstants.ACCOUNT_TYPE_SIM); + } else if (sub == PhoneConstants.SUB2) { + account = new Account(SimContactsConstants.SIM_NAME_2, + SimContactsConstants.ACCOUNT_TYPE_SIM); + } + } else { + if (sub == PhoneConstants.SUB1) { + account = new Account(SimContactsConstants.SIM_NAME, + SimContactsConstants.ACCOUNT_TYPE_SIM); + } + } + if (account == null) { + account = new Account(SimContactsConstants.PHONE_NAME, + SimContactsConstants.ACCOUNT_TYPE_PHONE); + } + return account; + } + + public static int getEnabledSimCount() { + int mPhoneCount = TelephonyManager.getDefault().getPhoneCount(); + int enabledSimCount = 0; + for (int i = 0; i < mPhoneCount; i++) { + if (TelephonyManager.SIM_STATE_READY == TelephonyManager + .getDefault().getSimState(i)) { + enabledSimCount++; + } + } + return enabledSimCount; + } + + public static int getSimFreeCount(Context context, int sub) { + String accountName = getAcount(sub).name; + int count = 0; + + if (context == null) { + return 0; + } + + Cursor queryCursor = context.getContentResolver().query( + RawContacts.CONTENT_URI, + new String[] { + RawContacts._ID + }, + RawContacts.ACCOUNT_NAME + " = '" + accountName + "' AND " + RawContacts.DELETED + + " = 0", null, null); + if (queryCursor != null) { + try { + count = queryCursor.getCount(); + } finally { + queryCursor.close(); + } + } + return getAdnCount(sub) - count; + } + + public static int getSpareAnrCount(int sub) { + int anrCount = 0; + int[] subId=SubscriptionManager.getSubId(sub); + try { + IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(ServiceManager + .getService(PHONEBOOK)); + if (iccIpb != null) { + if (subId != null + && TelephonyManager.getDefault().isMultiSimEnabled()) { + anrCount = iccIpb.getSpareAnrCountUsingSubId(subId[0]); + } else { + anrCount = iccIpb.getSpareAnrCount(); + } + } + } catch (RemoteException ex) { + // ignore it + } catch (SecurityException ex) { + Log.i(TAG, ex.toString()); + } catch (Exception ex) { + } + if (DBG) { + Log.d(TAG, "getSpareAnrCount(" + sub + ") = " + anrCount); + } + return anrCount; + } + + public static int getSpareEmailCount(int sub) { + int emailCount = 0; + int[] subId=SubscriptionManager.getSubId(sub); + try { + IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(ServiceManager + .getService(PHONEBOOK)); + if (iccIpb != null) { + if (subId != null + && TelephonyManager.getDefault().isMultiSimEnabled()) { + emailCount = iccIpb.getSpareEmailCountUsingSubId(subId[0]); + } else { + emailCount = iccIpb.getSpareEmailCount(); + } + } + } catch (RemoteException ex) { + // ignore it + } catch (SecurityException ex) { + Log.i(TAG, ex.toString()); + } catch (Exception ex) { + } + if (DBG) { + Log.d(TAG, "getSpareEmailCount(" + sub + ") = " + emailCount); + } + return emailCount; + } + + private static boolean hasChinese(String name) { + return name != null && name.getBytes().length > name.length(); + } + + /** + * Get SIM card aliases name, which defined in Settings + */ + public static String getMultiSimAliasesName(Context context, int subscription) { + if (context == null || subscription < 0) { + return null; + } + String name = ""; + if (TelephonyManager.getDefault().isMultiSimEnabled()) { + name = Settings.System.getString(context.getContentResolver(), + MULTI_SIM_NAME[subscription]); + } + if (TextUtils.isEmpty(name)) { + name = getSimAccountName(subscription); + } + return name; + } + + /** + * Get SIM card icon index by subscription + */ + public static int getCurrentSimIconIndex(Context context, int subscription) { + if (context == null || subscription < PhoneConstants.SUB1 + || subscription >= TelephonyManager.getDefault().getPhoneCount()) { + return -1; + } + + String simIconIndex = Settings.System.getString(context.getContentResolver(), + PREFERRED_SIM_ICON_INDEX); + if (TextUtils.isEmpty(simIconIndex)) { + return subscription; + } else { + String[] indexs = simIconIndex.split(","); + if (subscription >= indexs.length) { + return -1; + } + return Integer.parseInt(indexs[subscription]); + } + } + + + /** + * Display IP call setting dialog + */ + public static void showNoIPNumberDialog(final Context mContext, final int subscription) { + try { + new AlertDialog.Builder(mContext) + .setTitle(R.string.no_ip_number) + .setMessage(R.string.no_ip_number_on_sim_card) + .setPositiveButton(R.string.set_ip_number, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + setIPNumber(mContext, subscription); + } + }).setNegativeButton(android.R.string.cancel, null).show(); + } catch (Exception e) { + } + } + + /** + * Setting IP Call number + */ + public static void setIPNumber(final Context mContext, final int subscription) { + try { + LayoutInflater mInflater = LayoutInflater.from(mContext); + View v = mInflater.inflate(R.layout.ip_prefix_dialog, null); + final EditText edit = (EditText) v.findViewById(R.id.ip_prefix_dialog_edit); + String ip_prefix = Settings.System.getString(mContext.getContentResolver(), + IPCALL_PREFIX[subscription]); + edit.setText(ip_prefix); + + new AlertDialog.Builder(mContext).setTitle(R.string.ipcall_dialog_title) + .setIcon(android.R.drawable.ic_dialog_info).setView(v) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String ip_prefix = edit.getText().toString(); + Settings.System.putString(mContext.getContentResolver(), + IPCALL_PREFIX[subscription], ip_prefix); + } + }).setNegativeButton(android.R.string.cancel, null).show(); + } catch (Exception e) { + } + } + + /** + * Check one SIM card is enabled + */ + public static boolean isMultiSimEnable(Context context, int slotId) { + if (Settings.System.getInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) != 0 + || TelephonyManager.SIM_STATE_READY != TelephonyManager + .getDefault() + .getSimState(slotId)) { + return false; + } + return true; + } + + /** + * Get IP Call prefix + */ + public static String getIPCallPrefix(Context context, int slot) { + String ipCallPrefix = ""; + if (!TelephonyManager.getDefault().isMultiSimEnabled()) { + ipCallPrefix = Settings.System.getString(context.getContentResolver(), + "ipcall_prefix"); + } else { + ipCallPrefix = Settings.System.getString(context.getContentResolver(), + IPCALL_PREFIX[slot]); + } + return ipCallPrefix; + } + + public static PhoneAccountHandle getAccount(int slot) { + ComponentName serviceName = new ComponentName("com.android.phone", + "com.android.services.telephony.TelephonyConnectionService"); + int[] subId = SubscriptionManager.getSubId(slot); + return new PhoneAccountHandle(serviceName, String.valueOf(subId[0])); + } } diff --git a/src/com/android/contacts/common/SimContactsConstants.java b/src/com/android/contacts/common/SimContactsConstants.java new file mode 100644 index 00000000..9e3510a9 --- /dev/null +++ b/src/com/android/contacts/common/SimContactsConstants.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015, 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.common; + + +public interface SimContactsConstants { + + public static final String SIM_NAME_1 = "SIM1"; + public static final String SIM_NAME_2 = "SIM2"; + public static final String SIM_NAME = "SIM"; + public static final String PHONE_NAME = "PHONE"; + public static final String ACCOUNT_TYPE_SIM = "com.android.sim"; + public static final String ACCOUNT_TYPE_PHONE = "com.android.localphone"; + 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 INTENT_EXPORT_COMPLETE = + "com.android.sim.INTENT_EXPORT_COMPLETE"; + 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 WITHOUT_SIM_FLAG ="no_sim"; + public static final String IS_CONTACT = "is_contact"; + public static final String RESULT_KEY = "result"; + 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"; + public static final String ACTION_MULTI_PICK_CALL = + "com.android.contacts.action.MULTI_PICK_CALL"; + public static final String ACTION_MULTI_PICK_SIM = + "com.android.contacts.action.MULTI_PICK_SIM"; +} + + diff --git a/src/com/android/contacts/common/SimContactsOperation.java b/src/com/android/contacts/common/SimContactsOperation.java new file mode 100755 index 00000000..12ce1361 --- /dev/null +++ b/src/com/android/contacts/common/SimContactsOperation.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2015, 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.common; + +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.TelephonyManager; +import android.telephony.SubscriptionManager; +import android.text.TextUtils; +import android.util.Log; + +import com.android.contacts.common.SimContactsConstants; + +public class SimContactsOperation { + + private static final String TAG = "SimContactsOperation"; + private static final boolean DBG = true; + private static final int QUERY_TOKEN = 0; + private static final int INSERT_TOKEN = 1; + private static final int UPDATE_TOKEN = 2; + private static final int DELETE_TOKEN = 3; + + public static final String[] ACCOUNT_PROJECTION = new String[] { + RawContacts._ID, + RawContacts.CONTACT_ID, + RawContacts.ACCOUNT_NAME, + RawContacts.ACCOUNT_TYPE, + }; + + + private static final int ACCOUNT_COLUMN_RAW_ID = 0; + private static final int ACCOUNT_COLUMN_CONTACT_ID = 1; + private static final int ACCOUNT_COLUMN_NAME = 2; + private static final int ACCOUNT_COLUMN_TYPE = 3; + private static final int ACCOUNT_COLUMN_PHONE_NAME = 4; + + + + private static Context mContext; + private ContentResolver mResolver; + private ContentValues mValues = new ContentValues(); + + + public SimContactsOperation(Context context) { + this.mContext = context; + this.mResolver = context.getContentResolver(); + } + + + public Uri insert(ContentValues values, int subscription) { + + Uri uri = getContentUri(subscription); + 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; + 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)); + 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) { + int 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\\,\\;\\*\\#\\+\\:]", ""); + Uri uri = getContentUri(subscription); + + + 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 subscription) { + Uri uri = null; + int[] subId = SubscriptionManager.getSubId(subscription); + + if (subId != null && TelephonyManager.from(mContext).isMultiSimEnabled()) { + uri = Uri.parse(SimContactsConstants.SIM_SUB_URI + subId[0]); + } else { + uri = Uri.parse(SimContactsConstants.SIM_URI); + } + return uri; + } + + private static Cursor setupAccountCursor(long contactId) { + ContentResolver resolver = mContext.getContentResolver(); + + Cursor cursor = null; + try { + cursor = resolver.query(RawContacts.CONTENT_URI, + ACCOUNT_PROJECTION, + RawContacts.CONTACT_ID + "=" + + Long.toString(contactId), null, null); + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } finally { + if (cursor != null && cursor.moveToFirst()) { + return cursor; + } + if (cursor != null) { + cursor.close(); + } + cursor = null; + return null; + } + } + + public static ContentValues getSimAccountValues(long contactId) { + ContentValues mValues = new ContentValues(); + Cursor cursor = setupAccountCursor(contactId); + if (cursor == null || cursor.getCount() == 0) { + mValues.clear(); + return mValues; + } + long rawContactId = cursor.getLong(cursor.getColumnIndex(RawContacts._ID)); + String accountName = cursor.getString(cursor.getColumnIndex(RawContacts.ACCOUNT_NAME)); + String accountType = cursor.getString(cursor.getColumnIndex(RawContacts.ACCOUNT_TYPE)); + cursor.close(); + if (SimContactsConstants.ACCOUNT_TYPE_SIM.equals(accountType)) { + mValues.clear(); + String name = getContactItems(rawContactId,StructuredName.CONTENT_ITEM_TYPE, + ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); + mValues.put(SimContactsConstants.STR_TAG,name); + + String number = getContactPhoneNumber(rawContactId, + Phone.CONTENT_ITEM_TYPE, String.valueOf(Phone.TYPE_MOBILE), + ContactsContract.CommonDataKinds.Phone.DATA); + mValues.put(SimContactsConstants.STR_NUMBER,number); + + int sub = getSimSubscription(contactId); + + if (MoreContactUtils.canSaveAnr(sub)) { + String anrs = getContactPhoneNumber(rawContactId, + Phone.CONTENT_ITEM_TYPE, String.valueOf(Phone.TYPE_HOME), + ContactsContract.CommonDataKinds.Phone.DATA); + mValues.put(SimContactsConstants.STR_ANRS, anrs); + } + + if (MoreContactUtils.canSaveEmail(sub)) { + String emails = getContactItems(rawContactId, + Email.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Email.DATA); + mValues.put(SimContactsConstants.STR_EMAILS, emails); + } + } + return mValues; + } + + public static int getSimSubscription(long contactId) { + int subscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + Cursor cursor = setupAccountCursor(contactId); + 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) { + return subscription; + } + if (SimContactsConstants.ACCOUNT_TYPE_SIM.equals(accountType)) { + subscription = MoreContactUtils.getSubscription(accountType, accountName); + } + cursor.close(); + return subscription; + } + + + private static String getContactItems(long rawContactId, String selectionArg, + String columnName) { + StringBuilder retval = new StringBuilder(); + Uri baseUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); + Uri dataUri = Uri.withAppendedPath(baseUri, RawContacts.Data.CONTENT_DIRECTORY); + + Cursor c = null; + try { + c = mContext.getContentResolver().query(dataUri, null, + Data.MIMETYPE + "=?", new String[] {selectionArg}, null); + if (c == null || c.getCount() == 0) { + if(c != null) { + c.close(); + } + return null; + } + c.moveToPosition(-1); + + while (c.moveToNext()) { + if (!TextUtils.isEmpty(retval.toString())) { + retval.append(SimContactsConstants.EMAIL_SEP); + } + String value = c.getString(c.getColumnIndex(columnName)); + if (!TextUtils.isEmpty(value)) { + retval.append(value); + } + } + + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } finally { + if (c != null) { + c.close(); + } + } + + return retval.toString(); + } + + private static + String getContactPhoneNumber(long rawContactId, String selectionArg1, + String selectionArg2, String columnName) { + StringBuilder retval = new StringBuilder(); + Uri baseUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); + Uri dataUri = Uri.withAppendedPath(baseUri, RawContacts.Data.CONTENT_DIRECTORY); + + Cursor c = null; + try { + c = mContext.getContentResolver().query(dataUri, null, + Data.MIMETYPE + "=? AND " + Phone.TYPE + "=?", + new String[] {selectionArg1,selectionArg2}, null); + if (c == null || c.getCount() == 0) { + if(c != null) { + c.close(); + } + return null; + } + c.moveToPosition(-1); + + while (c.moveToNext()) { + if (!TextUtils.isEmpty(retval.toString())) { + retval.append(SimContactsConstants.ANR_SEP); + } + retval.append(c.getString(c.getColumnIndex(columnName))); + } + + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } finally { + if (c != null) { + c.close(); + } + } + + return retval.toString(); + } + +} diff --git a/src/com/android/contacts/common/editor/SelectAccountDialogFragment.java b/src/com/android/contacts/common/editor/SelectAccountDialogFragment.java index c2ebbbfa..26fc7712 100644 --- a/src/com/android/contacts/common/editor/SelectAccountDialogFragment.java +++ b/src/com/android/contacts/common/editor/SelectAccountDialogFragment.java @@ -65,7 +65,7 @@ public final class SelectAccountDialogFragment extends DialogFragment { final SelectAccountDialogFragment instance = new SelectAccountDialogFragment(); instance.setArguments(args); instance.setTargetFragment(targetFragment, 0); - instance.show(fragmentManager, null); + instance.show(fragmentManager, SelectAccountDialogFragment.TAG); } @Override diff --git a/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java b/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java index 6553627f..d37b93f1 100644..100755 --- a/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java +++ b/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java @@ -16,20 +16,41 @@ package com.android.contacts.common.interactions; +import android.accounts.Account; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.FragmentManager; +import android.app.ProgressDialog; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.ContentProviderOperation; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; import android.content.DialogInterface; import android.content.DialogInterface.OnDismissListener; +import android.content.DialogInterface.OnCancelListener; import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Resources; +import android.content.OperationApplicationException; import android.database.Cursor; +import android.text.TextUtils; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.RawContacts; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.provider.Settings; import android.telephony.PhoneNumberUtils; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -43,8 +64,13 @@ import android.widget.ArrayAdapter; import android.widget.TextView; import android.widget.Toast; +import com.android.contacts.common.MoreContactUtils; +import com.android.contacts.common.SimContactsOperation; +import com.android.contacts.common.SimContactsConstants; import com.android.contacts.common.R; import com.android.contacts.common.editor.SelectAccountDialogFragment; +import com.android.contacts.common.list.AccountFilterActivity; +import com.android.contacts.common.list.ContactListFilter; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contacts.common.util.AccountSelectionUtil; @@ -53,8 +79,11 @@ import com.android.contacts.common.util.ImplicitIntentsUtil; import com.android.contacts.common.vcard.ExportVCardActivity; import com.android.contacts.common.vcard.VCardCommonArguments; import com.android.contacts.commonbind.analytics.AnalyticsUtil; +import com.android.internal.telephony.PhoneConstants; import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; /** * An dialog invoked to import/export contacts. @@ -67,10 +96,67 @@ public class ImportExportDialogFragment extends DialogFragment private static final String KEY_SUBSCRIPTION_ID = "subscriptionId"; private static final String ARG_CONTACTS_ARE_AVAILABLE = "CONTACTS_ARE_AVAILABLE"; + private static final boolean DEBUG = true; + + public static final int SUBACTIVITY_EXPORT_CONTACTS = 100; + + // This values must be consistent with ImportExportDialogFragment.SUBACTIVITY_EXPORT_CONTACTS. + // This values is set 101,That is avoid to conflict with other new subactivity. + public static final int SUBACTIVITY_SHARE_VISILBLE_CONTACTS = 101; + public static final int MAX_COUNT_ALLOW_SHARE_CONTACT = 2000; + private final String[] LOOKUP_PROJECTION = new String[] { Contacts.LOOKUP_KEY }; + static final int PHONE_ID_COLUMN_INDEX = 0; + static final int PHONE_TYPE_COLUMN_INDEX = 1; + static final int PHONE_LABEL_COLUMN_INDEX = 2; + static final int PHONE_NUMBER_COLUMN_INDEX = 3; + static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 4; + static final int PHONE_CONTACT_ID_COLUMN_INDEX = 5; + // This value needs to start at 7. See {@link PeopleActivity}. + public static final int SUBACTIVITY_MULTI_PICK_CONTACT = 7; + //TODO: we need to refactor the export code in future release. + // QRD enhancement: export subscription selected by user + public static int mExportSub; + //this flag is the same as defined in MultiPickContactActivit + private static final String EXT_NOT_SHOW_SIM_FLAG = "not_sim_show"; + // QRD enhancement: Toast handler for exporting concat to sim card + private static final int TOAST_EXPORT_FAILED = 0; + private static final int TOAST_EXPORT_FINISHED = 1; + // only for sim card is full + private static final int TOAST_SIM_CARD_FULL = 2; + // only for contact name too long + private static final int TOAST_CONTACT_NAME_TOO_LONG = 3; + // there is a case export is canceled by user + private static final int TOAST_EXPORT_CANCELED = 4; + // only for not have phone number or email address + private static final int TOAST_EXPORT_NO_PHONE_OR_EMAIL = 5; + // only for sim contacts haven't been loaded completely + private static final int TOAST_SIM_CARD_NOT_LOAD_COMPLETE = 6; + private SimContactsOperation mSimContactsOperation; + private ArrayAdapter<AdapterEntry> mAdapter; + private Activity mActivity; + private static boolean isExportingToSIM = false; + public static boolean isExportingToSIM(){ + return isExportingToSIM; + } + private static ExportToSimThread mExportThread = null; + public ExportToSimThread createExportToSimThread(int subscription, + ArrayList<String[]> contactList, Activity mActivity) { + if (mExportThread == null) + mExportThread = new ExportToSimThread(subscription, contactList, mActivity); + return mExportThread; + } + + public static void destroyExportToSimThread(){ + mExportThread = null; + } + public void showExportToSIMProgressDialog(Activity activity){ + mExportThread.showExportProgressDialog(activity); + } + private SubscriptionManager mSubscriptionManager; /** Preferred way to show this dialog */ @@ -93,6 +179,7 @@ public class ImportExportDialogFragment extends DialogFragment @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Wrap our context to inflate list items using the correct theme + mActivity = getActivity(); final Resources res = getActivity().getResources(); final LayoutInflater dialogInflater = (LayoutInflater)getActivity() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -101,7 +188,7 @@ public class ImportExportDialogFragment extends DialogFragment VCardCommonArguments.ARG_CALLING_ACTIVITY); // Adapter that shows a list of string resources - final ArrayAdapter<AdapterEntry> adapter = new ArrayAdapter<AdapterEntry>(getActivity(), + mAdapter = new ArrayAdapter<AdapterEntry>(getActivity(), R.layout.select_dialog_item) { @Override public View getView(int position, View convertView, ViewGroup parent) { @@ -113,170 +200,138 @@ public class ImportExportDialogFragment extends DialogFragment } }; - final TelephonyManager manager = - (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); - - mSubscriptionManager = SubscriptionManager.from(getActivity()); - - if (res.getBoolean(R.bool.config_allow_import_from_vcf_file)) { - adapter.add(new AdapterEntry(getString(R.string.import_from_vcf_file), - R.string.import_from_vcf_file)); - } - if (manager != null && res.getBoolean(R.bool.config_allow_sim_import)) { - List<SubscriptionInfo> subInfoRecords = null; - try { - subInfoRecords = mSubscriptionManager.getActiveSubscriptionInfoList(); - } catch (SecurityException e) { - Log.w(TAG, "SecurityException thrown, lack permission for" - + " getActiveSubscriptionInfoList", e); - } - if (subInfoRecords != null) { - if (subInfoRecords.size() == 1) { - adapter.add(new AdapterEntry(getString(R.string.manage_sim_contacts), - R.string.manage_sim_contacts, - subInfoRecords.get(0).getSubscriptionId())); - adapter.add(new AdapterEntry(getString(R.string.export_to_sim), - R.string.export_to_sim, - subInfoRecords.get(0).getSubscriptionId())); - } else { - for (SubscriptionInfo record : subInfoRecords) { - adapter.add(new AdapterEntry(getSubDescription(record, true), - R.string.manage_sim_contacts, record.getSubscriptionId())); - adapter.add(new AdapterEntry(getSubDescription(record, false), - R.string.export_to_sim, record.getSubscriptionId())); - } - } - } - } - if (res.getBoolean(R.bool.config_allow_export)) { - if (contactsAreAvailable) { - adapter.add(new AdapterEntry(getString(R.string.export_to_vcf_file), - R.string.export_to_vcf_file)); - } - } - if (res.getBoolean(R.bool.config_allow_share_visible_contacts)) { - if (contactsAreAvailable) { - adapter.add(new AdapterEntry(getString(R.string.share_visible_contacts), - R.string.share_visible_contacts)); - } - } + // Manually call notifyDataSetChanged() to refresh the list. + mAdapter.setNotifyOnChange(false); + loadData(contactsAreAvailable); final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - boolean dismissDialog; - final int resId = adapter.getItem(which).mChoiceResourceId; + final int resId = mAdapter.getItem(which).mChoiceResourceId; switch (resId) { - case R.string.manage_sim_contacts: + case R.string.import_from_sim: { + handleImportFromSimRequest(resId); + break; + } case R.string.import_from_vcf_file: { - dismissDialog = handleImportRequest(resId, - adapter.getItem(which).mSubscriptionId); + handleImportRequest(resId); + break; + } + case R.string.export_to_sim: { + handleExportToSimRequest(resId); break; } case R.string.export_to_vcf_file: { - dismissDialog = true; - Intent exportIntent = new Intent(getActivity(), ExportVCardActivity.class); - exportIntent.putExtra(VCardCommonArguments.ARG_CALLING_ACTIVITY, - callingActivity); - getActivity().startActivity(exportIntent); + Intent exportIntent = new Intent(SimContactsConstants.ACTION_MULTI_PICK, + Contacts.CONTENT_URI); + exportIntent.putExtra(SimContactsConstants.IS_CONTACT, true); + getActivity().startActivityForResult(exportIntent, + SUBACTIVITY_EXPORT_CONTACTS); break; } case R.string.share_visible_contacts: { - dismissDialog = true; doShareVisibleContacts(); break; } - case R.string.export_to_sim: { - dismissDialog = true; - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setClassName("com.android.phone", - "com.android.phone.ExportContactsToSim"); - intent.putExtra("subscription_id", adapter.getItem(which).mSubscriptionId); - startActivity(intent); - break; - } default: { - dismissDialog = true; Log.e(TAG, "Unexpected resource: " + getActivity().getResources().getResourceEntryName(resId)); } } - if (dismissDialog) { - dialog.dismiss(); - } + dialog.dismiss(); } }; return new AlertDialog.Builder(getActivity()) .setTitle(contactsAreAvailable ? R.string.dialog_import_export : R.string.dialog_import) - .setSingleChoiceItems(adapter, -1, clickListener) + .setSingleChoiceItems(mAdapter, -1, clickListener) .create(); } - private void doShareVisibleContacts() { - try { - // TODO move the query into a loader and do this in a background thread - final Cursor cursor = getActivity().getContentResolver().query(Contacts.CONTENT_URI, - LOOKUP_PROJECTION, Contacts.IN_VISIBLE_GROUP + "!=0", null, null); - if (cursor != null) { - try { - if (!cursor.moveToFirst()) { - Toast.makeText(getActivity(), R.string.share_error, Toast.LENGTH_SHORT) - .show(); - return; - } + /** + * Loading the menu list data. + * @param contactsAreAvailable + */ + private void loadData(boolean contactsAreAvailable) { + if (null == mActivity && null == mAdapter) { + return; + } - StringBuilder uriListBuilder = new StringBuilder(); - int index = 0; - do { - if (index != 0) - uriListBuilder.append(':'); - uriListBuilder.append(cursor.getString(0)); - index++; - } while (cursor.moveToNext()); - Uri uri = Uri.withAppendedPath( - Contacts.CONTENT_MULTI_VCARD_URI, - Uri.encode(uriListBuilder.toString())); - - final Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType(Contacts.CONTENT_VCARD_TYPE); - intent.putExtra(Intent.EXTRA_STREAM, uri); - ImplicitIntentsUtil.startActivityOutsideApp(getActivity(), intent); - } finally { - cursor.close(); - } + mAdapter.clear(); + final Resources res = mActivity.getResources(); + boolean hasIccCard = false; + if (TelephonyManager.getDefault().isMultiSimEnabled()) { + for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) { + hasIccCard = TelephonyManager.getDefault().hasIccCard(i); + if (hasIccCard) { + break; + } + } + } else { + hasIccCard = TelephonyManager.getDefault().hasIccCard(); + } + + if (hasIccCard + && res.getBoolean(R.bool.config_allow_sim_import)) { + mAdapter.add(new AdapterEntry(getString(R.string.import_from_sim), + R.string.import_from_sim)); + } + if (res.getBoolean(R.bool.config_allow_import_from_vcf_file)) { + mAdapter.add(new AdapterEntry(getString(R.string.import_from_vcf_file), + R.string.import_from_vcf_file)); + } + + if (hasIccCard) { + mAdapter.add(new AdapterEntry(getString(R.string.export_to_sim), + R.string.export_to_sim)); + } + if (res.getBoolean(R.bool.config_allow_export)) { + // If contacts are available and there is at least one contact in + // database, show "Export to SD card" menu item. Otherwise hide it + // because it makes no sense. + if (contactsAreAvailable) { + mAdapter.add(new AdapterEntry(getString(R.string.export_to_vcf_file), + R.string.export_to_vcf_file)); + } + } + if (res.getBoolean(R.bool.config_allow_share_visible_contacts)) { + if (contactsAreAvailable) { + mAdapter.add(new AdapterEntry(getString(R.string.share_visible_contacts), + R.string.share_visible_contacts)); } - } catch (Exception e) { - Log.e(TAG, "Sharing visible contacts failed", e); - Toast.makeText(getContext(), R.string.share_visible_contacts_failure, - Toast.LENGTH_SHORT).show(); } } + private void doShareVisibleContacts() { + Intent intent = new Intent(SimContactsConstants.ACTION_MULTI_PICK); + intent.setType(Contacts.CONTENT_TYPE); + intent.putExtra(SimContactsConstants.IS_CONTACT,true); + getActivity().startActivityForResult(intent, SUBACTIVITY_SHARE_VISILBLE_CONTACTS); + } + /** * Handle "import from SIM" and "import from SD". * * @return {@code true} if the dialog show be closed. {@code false} otherwise. */ - private boolean handleImportRequest(int resId, int subscriptionId) { + private boolean handleImportRequest(int resId) { // There are three possibilities: // - more than one accounts -> ask the user // - just one account -> use the account without asking the user // - no account -> use phone-local storage without asking the user - final AccountTypeManager accountTypes = AccountTypeManager.getInstance(getActivity()); + final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mActivity); final List<AccountWithDataSet> accountList = accountTypes.getAccounts(true); final int size = accountList.size(); if (size > 1) { // Send over to the account selector final Bundle args = new Bundle(); args.putInt(KEY_RES_ID, resId); - args.putInt(KEY_SUBSCRIPTION_ID, subscriptionId); - SelectAccountDialogFragment.show( - getFragmentManager(), this, - R.string.dialog_new_contact_account, - AccountListFilter.ACCOUNTS_CONTACT_WRITABLE, args); + SelectAccountDialogFragment.show(mActivity.getFragmentManager(), + this, R.string.dialog_new_contact_account, + AccountListFilter.ACCOUNTS_CONTACT_WRITABLE_WITHOUT_SIM, + args); // In this case, because this DialogFragment is used as a target fragment to // SelectAccountDialogFragment, we can't close it yet. We close the dialog when @@ -284,8 +339,8 @@ public class ImportExportDialogFragment extends DialogFragment return false; } - AccountSelectionUtil.doImport(getActivity(), resId, - (size == 1 ? accountList.get(0) : null), subscriptionId); + AccountSelectionUtil.doImport(mActivity, resId, + (size == 1 ? accountList.get(0) : null)); return true; // Close the dialog. } @@ -294,8 +349,7 @@ public class ImportExportDialogFragment extends DialogFragment */ @Override public void onAccountChosen(AccountWithDataSet account, Bundle extraArgs) { - AccountSelectionUtil.doImport(getActivity(), extraArgs.getInt(KEY_RES_ID), - account, extraArgs.getInt(KEY_SUBSCRIPTION_ID)); + AccountSelectionUtil.doImport(mActivity, extraArgs.getInt(KEY_RES_ID), account); // At this point the dialog is still showing (which is why we can use getActivity() above) // So close it. @@ -308,6 +362,478 @@ public class ImportExportDialogFragment extends DialogFragment dismiss(); } + private class ExportToSimSelectListener implements DialogInterface.OnClickListener { + public void onClick(DialogInterface dialog, int which) { + if (which >= 0) { + mExportSub = which; + } else if (which == DialogInterface.BUTTON_POSITIVE) { + Intent pickPhoneIntent = new Intent( + SimContactsConstants.ACTION_MULTI_PICK, Contacts.CONTENT_URI); + // do not show the contacts in SIM card + pickPhoneIntent.putExtra(AccountFilterActivity.KEY_EXTRA_CONTACT_LIST_FILTER, + ContactListFilter + .createFilterWithType(ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS)); + pickPhoneIntent.putExtra(EXT_NOT_SHOW_SIM_FLAG, true); + pickPhoneIntent.putExtra(SimContactsConstants.IS_CONTACT,true); + mActivity.startActivityForResult(pickPhoneIntent, SUBACTIVITY_MULTI_PICK_CONTACT); + } + } + } + + public class ImportFromSimSelectListener implements DialogInterface.OnClickListener { + public void onClick(DialogInterface dialog, int which) { + if (which >= 0) { + AccountSelectionUtil.setImportSubscription(which); + } else if (which == DialogInterface.BUTTON_POSITIVE) { + handleImportRequest(R.string.import_from_sim); + } + } + } + + /** + * A thread that export contacts to sim card + */ + public class ExportToSimThread extends Thread { + private int subscription; + private boolean canceled; + private ArrayList<String[]> contactList; + private ProgressDialog mExportProgressDlg; + private ContentValues mValues = new ContentValues(); + Activity mPeople; + private int freeSimCount = 0; + + public ExportToSimThread(int subscription, ArrayList<String[]> contactList, + Activity mActivity) { + super(); + this.subscription = subscription; + this.contactList = contactList; + canceled = false; + mPeople = mActivity; + showExportProgressDialog(mPeople); + } + + @Override + public void run() { + isExportingToSIM = true; + Account account = MoreContactUtils.getAcount(subscription); + boolean isAirplaneMode = false; + boolean isSimCardFull = false; + boolean isSimCardLoaded = true; + // GoogleSource.createMyContactsIfNotExist(account, getActivity()); + // in case export is stopped, record the count of inserted successfully + int insertCount = 0; + + mSimContactsOperation = new SimContactsOperation(mPeople); + Cursor cr = null; + // call query first, otherwise insert will fail if this insert is called + // without any query before + try{ + int[] subId = SubscriptionManager.getSubId(subscription); + if (subId != null + && TelephonyManager.getDefault().isMultiSimEnabled()) { + cr = mPeople.getContentResolver().query( + Uri.parse(SimContactsConstants.SIM_SUB_URI + + subId[0]), null, null, null, null); + } else { + cr = mPeople.getContentResolver().query( + Uri.parse(SimContactsConstants.SIM_URI), null, + null, null, null); + } + } catch (NullPointerException e) { + Log.e(TAG, "Exception:" + e); + } finally { + if (cr != null) { + cr.close(); + } + } + freeSimCount = MoreContactUtils.getSimFreeCount(mPeople,subscription); + boolean canSaveAnr = MoreContactUtils.canSaveAnr(subscription); + boolean canSaveEmail = MoreContactUtils.canSaveEmail(subscription); + int emptyAnr = MoreContactUtils.getSpareAnrCount(subscription); + int emptyEmail = MoreContactUtils + .getSpareEmailCount(subscription); + int emptyNumber = freeSimCount + emptyAnr; + + Log.d(TAG, "freeSimCount = " + freeSimCount); + String emails = null; + if (contactList != null) { + Iterator<String[]> iterator = contactList.iterator(); + while (iterator.hasNext() && !canceled && !isAirplaneMode && isSimCardLoaded) { + String[] contactInfo = iterator.next(); + //contacts name has been existed in contactInfo,so no need query it again + String name = contactInfo[4]; + ArrayList<String> arrayNumber = new ArrayList<String>(); + ArrayList<String> arrayEmail = new ArrayList<String>(); + + Uri dataUri = Uri.withAppendedPath( + ContentUris.withAppendedId(Contacts.CONTENT_URI, + Long.parseLong(contactInfo[1])), + Contacts.Data.CONTENT_DIRECTORY); + final String[] projection = new String[] { + Contacts._ID, Contacts.Data.MIMETYPE, Contacts.Data.DATA1, + }; + Cursor c = mPeople.getContentResolver().query(dataUri, projection, null, + null, null); + + if (c != null && c.moveToFirst()) { + do { + String mimeType = c.getString(1); + 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()); + } + 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 phoneCountInOneSimContact = 1; + int emailCountInOneSimContact = 0; + if (canSaveAnr) { + int num = MoreContactUtils.getOneSimAnrCount(subscription); + phoneCountInOneSimContact = num > 1 ? (num + 1) : 2; + } + if (canSaveEmail) { + emailCountInOneSimContact = MoreContactUtils + .getOneSimEmailCount(subscription); + } + 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 i = 0; i < groupCount; i++) { + 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); + if (s.length() > MoreContactUtils + .MAX_LENGTH_NUMBER_IN_SIM) { + s = s.substring(0, + MoreContactUtils.MAX_LENGTH_NUMBER_IN_SIM); + } + 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); + if (s.length() > MoreContactUtils + .MAX_LENGTH_EMAIL_IN_SIM) { + s = s.substring(0, + MoreContactUtils.MAX_LENGTH_EMAIL_IN_SIM); + } + email.append(s); + email.append(SimContactsConstants.EMAIL_SEP); + } + } + } + + result = MoreContactUtils.insertToCard(mPeople, name, num, + email.toString(), anrNum.toString(), subscription); + + if (null == result) { + // add toast handler when sim card is full + if ((MoreContactUtils.getAdnCount(subscription) > 0) + && (MoreContactUtils.getSimFreeCount(mPeople, + subscription) == 0)) { + isSimCardFull = true; + mToastHandler.sendMessage(mToastHandler.obtainMessage( + TOAST_SIM_CARD_FULL, insertCount, 0)); + break; + } else { + isAirplaneMode = MoreContactUtils + .isAPMOnAndSIMPowerDown(mPeople); + if (isAirplaneMode) { + mToastHandler.sendMessage(mToastHandler.obtainMessage( + TOAST_EXPORT_FAILED, insertCount, 0)); + break; + } else { + // 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; + } + continue; + } + } + } else { + if (DEBUG) { + Log.d(TAG, "Exported contact [" + name + ", " + + contactInfo[0] + ", " + contactInfo[1] + + "] to sub " + subscription); + } + insertCount++; + freeSimCount--; + } + } else { + if (MoreContactUtils.getAdnCount(subscription) == 0) { + isSimCardLoaded = false; + mToastHandler.sendEmptyMessage( + TOAST_SIM_CARD_NOT_LOAD_COMPLETE); + } else { + isSimCardFull = true; + mToastHandler.sendMessage(mToastHandler.obtainMessage( + TOAST_SIM_CARD_FULL, insertCount, 0)); + } + break; + } + } + + if (isSimCardFull) { + break; + } + } + } + if (mExportProgressDlg != null) { + mExportProgressDlg.dismiss(); + mExportProgressDlg = null; + } + + if (!isAirplaneMode && !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); + } + } + isExportingToSIM = false; + Intent intent = new Intent(SimContactsConstants.INTENT_EXPORT_COMPLETE); + mPeople.sendBroadcast(intent); + } + + private Handler mToastHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + int exportCount = 0; + switch (msg.what) { + case TOAST_EXPORT_FAILED: + exportCount = msg.arg1; + Toast.makeText(mPeople, mPeople.getString(R.string.export_to_sim_failed, + exportCount), Toast.LENGTH_SHORT).show(); + break; + case TOAST_EXPORT_FINISHED: + Toast.makeText(mPeople, R.string.export_finished, Toast.LENGTH_SHORT) + .show(); + break; + + // add toast handler when sim card is full + case TOAST_SIM_CARD_FULL: + exportCount = msg.arg1; + Toast.makeText(mPeople, mPeople.getString(R.string.export_sim_card_full, + exportCount), Toast.LENGTH_SHORT).show(); + break; + + //add the max count limit of Chinese code or not + case TOAST_CONTACT_NAME_TOO_LONG: + Toast.makeText(mPeople, R.string.tag_too_long, Toast.LENGTH_SHORT).show(); + break; + + // add toast handler when export is canceled + case TOAST_EXPORT_CANCELED: + exportCount = msg.arg1; + Toast.makeText(mPeople,mPeople.getString(R.string.export_cancelled, + String.valueOf(exportCount)), Toast.LENGTH_SHORT).show(); + break; + + // add toast handler when no phone or email + case TOAST_EXPORT_NO_PHONE_OR_EMAIL: + String name = (String) msg.obj; + Toast.makeText(mPeople, + mPeople.getString(R.string.export_no_phone_or_email, name), + Toast.LENGTH_SHORT).show(); + break; + case TOAST_SIM_CARD_NOT_LOAD_COMPLETE: + Toast.makeText(mPeople, R.string.sim_contacts_not_load, + Toast.LENGTH_SHORT).show(); + break; + } + } + }; + + public void showExportProgressDialog(Activity activity){ + mPeople = activity; + mExportProgressDlg = new ProgressDialog(mPeople); + mExportProgressDlg.setTitle(R.string.export_to_sim); + mExportProgressDlg.setOnCancelListener(new OnCancelListener() { + public void onCancel(DialogInterface dialog) { + Log.d(TAG, "Cancel exporting contacts"); + canceled = true; + } + }); + mExportProgressDlg.setMessage(mPeople.getString(R.string.exporting)); + mExportProgressDlg.setProgressNumberFormat(mPeople.getString( + R.string.reading_vcard_files)); + mExportProgressDlg.setMax(contactList.size()); + //mExportProgressDlg.setProgress(insertCount); + + // set cancel dialog by touching outside disabled. + mExportProgressDlg.setCanceledOnTouchOutside(false); + + // add a cancel button to let user cancel explicitly. + mExportProgressDlg.setButton(DialogInterface.BUTTON_NEGATIVE, + mPeople.getString(R.string.progressdialog_cancel), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (DEBUG) { + Log.d(TAG, "Cancel exporting contacts by click button"); + } + canceled = true; + } + }); + + mExportProgressDlg.show(); + } + } + + public ImportFromSimSelectListener listener; + /** + * Create a {@link Dialog} that allows the user to pick from a bulk import + * or bulk export task across all contacts. + */ + private Dialog displayImportExportDialog(int id, Bundle bundle) { + Dialog diag; + switch (id) { + case R.string.import_from_sim_select: { + listener = new ImportFromSimSelectListener(); + showSimSelectDialog(); + break; + } + case R.string.export_to_sim: { + String[] items = new String[TelephonyManager.getDefault().getPhoneCount()]; + for (int i = 0; i < items.length; i++) { + items[i] = getString(R.string.export_to_sim) + ": " + + MoreContactUtils.getMultiSimAliasesName(mActivity, i); + } + mExportSub = PhoneConstants.SUB1; + ExportToSimSelectListener listener = new ExportToSimSelectListener(); + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.export_to_sim) + .setPositiveButton(android.R.string.ok, listener) + .setSingleChoiceItems(items, 0, listener).create(); + } + } + return null; + } + + public void showSimSelectDialog() { + AccountSelectionUtil.setImportSubscription(PhoneConstants.SUB1); + // item is for sim account to show + String[] items = new String[TelephonyManager.getDefault().getPhoneCount()]; + for (int i = 0; i < items.length; i++) { + items[i] = getString(R.string.import_from_sim) + ": " + + MoreContactUtils.getMultiSimAliasesName(mActivity, i); + } + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.import_from_sim) + .setPositiveButton(android.R.string.ok, listener) + .setSingleChoiceItems(items, 0, listener).create().show(); + } + + private void handleImportFromSimRequest(int Id) { + if (TelephonyManager.getDefault().isMultiSimEnabled()) { + if (MoreContactUtils.getEnabledSimCount() > 1) { + displayImportExportDialog(R.string.import_from_sim_select + ,null); + } else { + AccountSelectionUtil.setImportSubscription(getEnabledIccCard()); + handleImportRequest(Id); + } + } else { + handleImportRequest(Id); + } + } + + private void handleExportToSimRequest(int Id) { + if (MoreContactUtils.getEnabledSimCount() >1) { + //has two enalbed sim cards, prompt dialog to select one + displayImportExportDialog(Id, null).show(); + } else { + mExportSub = getEnabledIccCard(); + Intent pickPhoneIntent = new Intent( + SimContactsConstants.ACTION_MULTI_PICK, Contacts.CONTENT_URI); + // do not show the contacts in SIM card + pickPhoneIntent.putExtra(AccountFilterActivity.KEY_EXTRA_CONTACT_LIST_FILTER, + ContactListFilter + .createFilterWithType(ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS)); + pickPhoneIntent.putExtra(EXT_NOT_SHOW_SIM_FLAG, true); + pickPhoneIntent.putExtra(SimContactsConstants.IS_CONTACT,true); + mActivity.startActivityForResult(pickPhoneIntent, SUBACTIVITY_MULTI_PICK_CONTACT); + } + } + + private boolean hasEnabledIccCard(int subscription) { + return TelephonyManager.getDefault().hasIccCard(subscription) + && TelephonyManager.getDefault().getSimState(subscription) + == TelephonyManager.SIM_STATE_READY; + } + + private int getEnabledIccCard() { + for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) { + if (hasEnabledIccCard(i)) { + return i; + } + } + return PhoneConstants.SUB1; + } + private CharSequence getSubDescription(SubscriptionInfo record, boolean isImport) { CharSequence name = record.getDisplayName(); if (TextUtils.isEmpty(record.getNumber())) { diff --git a/src/com/android/contacts/common/lettertiles/LetterTileDrawable.java b/src/com/android/contacts/common/lettertiles/LetterTileDrawable.java index e62d4218..14548deb 100644 --- a/src/com/android/contacts/common/lettertiles/LetterTileDrawable.java +++ b/src/com/android/contacts/common/lettertiles/LetterTileDrawable.java @@ -16,6 +16,8 @@ package com.android.contacts.common.lettertiles; +import android.accounts.Account; +import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; @@ -28,8 +30,11 @@ import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.text.TextUtils; +import android.telephony.TelephonyManager; import android.util.Log; +import com.android.contacts.common.MoreContactUtils; +import com.android.contacts.common.model.account.SimAccountType; import com.android.contacts.common.R; import com.android.contacts.common.util.BitmapUtil; @@ -53,6 +58,9 @@ public class LetterTileDrawable extends Drawable { 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[MoreContactUtils.IC_SIM_PICTURE.length]; /** Reusable components to avoid new allocations */ private static final Paint sPaint = new Paint(); @@ -70,12 +78,18 @@ public class LetterTileDrawable extends Drawable { private int mContactType = TYPE_DEFAULT; private float mScale = 1.0f; private float mOffset = 0.0f; + private Account mAccount; + private Context mContext; private boolean mIsCircle = false; - public LetterTileDrawable(final Resources res) { + + public LetterTileDrawable(final Context context, final Resources res, + final Account account) { mPaint = new Paint(); mPaint.setFilterBitmap(true); mPaint.setDither(true); + mAccount = account; + mContext = context; if (sColors == null) { sColors = res.obtainTypedArray(R.array.letter_tile_colors); @@ -88,6 +102,13 @@ public class LetterTileDrawable extends Drawable { 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 < MoreContactUtils.IC_SIM_PICTURE.length; i++) { + DEFAULT_CUSTOMIZE_SIM_PERSON_AVATAR[i] = BitmapFactory + .decodeResource(res, MoreContactUtils.IC_SIM_PICTURE[i]); + } + sPaint.setTypeface(Typeface.create( res.getString(R.string.letter_tile_letter_font_family), Typeface.NORMAL)); sPaint.setTextAlign(Align.CENTER); @@ -143,7 +164,10 @@ public class LetterTileDrawable extends Drawable { } // Draw letter/digit only if the first character is an english letter - if (mDisplayName != null && isEnglishLetter(mDisplayName.charAt(0))) { + if (mDisplayName != null + && isEnglishLetter(mDisplayName.charAt(0)) + && (mAccount == null || (mAccount != null && !mAccount.type + .equals(SimAccountType.ACCOUNT_TYPE)))) { // Draw letter or digit. sFirstChar[0] = Character.toUpperCase(mDisplayName.charAt(0)); @@ -160,9 +184,9 @@ public class LetterTileDrawable extends Drawable { sPaint); } else { // Draw the default image if there is no letter/digit to be drawn - final Bitmap bitmap = getBitmapForContactType(mContactType); - drawBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight(), - canvas); + final Bitmap bitmap = getBitmapForContactType(mContactType, + mAccount, mContext); + drawBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight(), canvas); } } @@ -184,7 +208,21 @@ public class LetterTileDrawable extends Drawable { return sColors.getColor(color, sDefaultColor); } - private static Bitmap getBitmapForContactType(int contactType) { + private static Bitmap getBitmapForContactType(int contactType, + Account account, Context context) { + if (account != null && SimAccountType.ACCOUNT_TYPE.equals(account.type)) { + if (TelephonyManager.getDefault().isMultiSimEnabled()) { + final int sub = MoreContactUtils.getSubscription( + SimAccountType.ACCOUNT_TYPE, account.name); + int index = MoreContactUtils.getCurrentSimIconIndex(context, sub); + if (index < 0) { + return DEFAULT_PERSON_AVATAR; + } + return DEFAULT_CUSTOMIZE_SIM_PERSON_AVATAR[index]; + } else { + return DEFAULT_SIM_PERSON_AVATAR; + } + } switch (contactType) { case TYPE_PERSON: return DEFAULT_PERSON_AVATAR; diff --git a/src/com/android/contacts/common/list/AccountFilterActivity.java b/src/com/android/contacts/common/list/AccountFilterActivity.java index 58450c65..9dba5362 100644 --- a/src/com/android/contacts/common/list/AccountFilterActivity.java +++ b/src/com/android/contacts/common/list/AccountFilterActivity.java @@ -120,7 +120,8 @@ public class AccountFilterActivity extends Activity implements AdapterView.OnIte // Hide extensions with no raw_contacts. continue; } - Drawable icon = accountType != null ? accountType.getDisplayIcon(context) : null; + Drawable icon = accountType != null ? accountType.getDisplayIcon( + context, account.name) : null; accountFilters.add(ContactListFilter.createAccountFilter( account.type, account.name, account.dataSet, icon)); } diff --git a/src/com/android/contacts/common/list/ContactEntry.java b/src/com/android/contacts/common/list/ContactEntry.java index 43fc19dd..66ae50d1 100644 --- a/src/com/android/contacts/common/list/ContactEntry.java +++ b/src/com/android/contacts/common/list/ContactEntry.java @@ -16,6 +16,7 @@ package com.android.contacts.common.list; +import android.accounts.Account; import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.ContactsContract.PinnedPositions; @@ -36,6 +37,7 @@ public class ContactEntry { public int pinned = PinnedPositions.UNPINNED; public boolean isFavorite = false; public boolean isDefaultNumber = false; + public Account account; public static final ContactEntry BLANK_ENTRY = new ContactEntry(); }
\ No newline at end of file diff --git a/src/com/android/contacts/common/list/ContactEntryListAdapter.java b/src/com/android/contacts/common/list/ContactEntryListAdapter.java index 8500d816..63b5381d 100644 --- a/src/com/android/contacts/common/list/ContactEntryListAdapter.java +++ b/src/com/android/contacts/common/list/ContactEntryListAdapter.java @@ -15,6 +15,7 @@ */ package com.android.contacts.common.list; +import android.accounts.Account; import android.content.Context; import android.content.CursorLoader; import android.content.res.Resources; @@ -711,15 +712,24 @@ public abstract class ContactEntryListAdapter extends IndexerListAdapter { * @param contactIdColumn Index of the contact id column * @param lookUpKeyColumn Index of the lookup key column * @param displayNameColumn Index of the display name column + * @param accountTypeColume Index of the account type column + * @param accountNameColume Index of the account name column */ 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)); @@ -729,8 +739,8 @@ public abstract class ContactEntryListAdapter extends IndexerListAdapter { quickContact.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE); 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); @@ -739,8 +749,8 @@ public abstract class ContactEntryListAdapter extends IndexerListAdapter { 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/common/list/ContactEntryListFragment.java b/src/com/android/contacts/common/list/ContactEntryListFragment.java index 8d1cedec..b49e57c8 100644 --- a/src/com/android/contacts/common/list/ContactEntryListFragment.java +++ b/src/com/android/contacts/common/list/ContactEntryListFragment.java @@ -20,9 +20,11 @@ import android.app.Activity; import android.app.Fragment; import android.app.LoaderManager; import android.app.LoaderManager.LoaderCallbacks; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.CursorLoader; import android.content.Intent; +import android.content.IntentFilter; import android.content.Loader; import android.database.Cursor; import android.os.Bundle; @@ -50,6 +52,7 @@ import com.android.common.widget.CompositeCursorAdapter.Partition; import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.preference.ContactsPreferences; import com.android.contacts.common.util.ContactListViewUtils; +import com.android.internal.telephony.TelephonyIntents; import java.util.Locale; @@ -147,6 +150,13 @@ public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter private LoaderManager mLoaderManager; + private BroadcastReceiver mSIMStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context arg0, Intent arg1) { + reloadData(); + } + }; + private Handler mDelayedDirectorySearchHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -262,6 +272,14 @@ public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter restoreSavedState(savedState); mAdapter = createListAdapter(); mContactsPrefs = new ContactsPreferences(mContext); + restoreSavedState(savedState); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); + filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + if (mContext != null) { + mContext.registerReceiver(mSIMStateReceiver, filter); + } } public void restoreSavedState(Bundle savedState) { @@ -480,6 +498,14 @@ public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter mAdapter.clearPartitions(); } + @Override + public void onDestroy() { + super.onDestroy(); + if (mContext != null) { + mContext.unregisterReceiver(mSIMStateReceiver); + } + } + protected void reloadData() { removePendingDirectorySearchRequests(); mAdapter.onDataReload(); diff --git a/src/com/android/contacts/common/list/ContactListAdapter.java b/src/com/android/contacts/common/list/ContactListAdapter.java index 600d731a..4c91f3fb 100644 --- a/src/com/android/contacts/common/list/ContactListAdapter.java +++ b/src/com/android/contacts/common/list/ContactListAdapter.java @@ -15,12 +15,14 @@ */ package com.android.contacts.common.list; +import android.accounts.Account; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Directory; +import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.SearchSnippets; import android.text.TextUtils; import android.view.View; @@ -49,6 +51,8 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter { Contacts.PHOTO_THUMBNAIL_URI, // 5 Contacts.LOOKUP_KEY, // 6 Contacts.IS_USER_PROFILE, // 7 + RawContacts.ACCOUNT_TYPE, // 8 + RawContacts.ACCOUNT_NAME, // 9 }; private static final String[] CONTACT_PROJECTION_ALTERNATIVE = new String[] { @@ -60,6 +64,8 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter { Contacts.PHOTO_THUMBNAIL_URI, // 5 Contacts.LOOKUP_KEY, // 6 Contacts.IS_USER_PROFILE, // 7 + RawContacts.ACCOUNT_TYPE, // 8 + RawContacts.ACCOUNT_NAME, // 9 }; private static final String[] FILTER_PROJECTION_PRIMARY = new String[] { @@ -71,7 +77,9 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter { Contacts.PHOTO_THUMBNAIL_URI, // 5 Contacts.LOOKUP_KEY, // 6 Contacts.IS_USER_PROFILE, // 7 - SearchSnippets.SNIPPET, // 8 + RawContacts.ACCOUNT_TYPE, // 8 + RawContacts.ACCOUNT_NAME, // 9 + SearchSnippets.SNIPPET, // 10 }; private static final String[] FILTER_PROJECTION_ALTERNATIVE = new String[] { @@ -83,7 +91,9 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter { Contacts.PHOTO_THUMBNAIL_URI, // 5 Contacts.LOOKUP_KEY, // 6 Contacts.IS_USER_PROFILE, // 7 - SearchSnippets.SNIPPET, // 8 + RawContacts.ACCOUNT_TYPE, // 8 + RawContacts.ACCOUNT_NAME, // 9 + SearchSnippets.SNIPPET, // 10 }; public static final int CONTACT_ID = 0; @@ -94,7 +104,9 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter { public static final int CONTACT_PHOTO_URI = 5; public static final int CONTACT_LOOKUP_KEY = 6; public static final int CONTACT_IS_USER_PROFILE = 7; - public static final int CONTACT_SNIPPET = 8; + public static final int CONTACT_ACCOUNT_TYPE = 8; + public static final int CONTACT_ACCOUNT_NAME = 9; + public static final int CONTACT_SNIPPET = 10; } private CharSequence mUnknownNameText; @@ -230,8 +242,15 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter { 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); @@ -242,7 +261,7 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter { 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/common/list/ContactListFilter.java b/src/com/android/contacts/common/list/ContactListFilter.java index f81ea742..b48efb98 100644 --- a/src/com/android/contacts/common/list/ContactListFilter.java +++ b/src/com/android/contacts/common/list/ContactListFilter.java @@ -45,6 +45,7 @@ public final class ContactListFilter implements Comparable<ContactListFilter>, P * TODO: "group" filter and relevant variables are all obsolete. Remove them. */ private static final int FILTER_TYPE_GROUP = 1; + public static final int FILTER_TYPE_ALL_WITHOUT_SIM = 2; private static final String KEY_FILTER_TYPE = "filter.type"; private static final String KEY_ACCOUNT_NAME = "filter.accountName"; diff --git a/src/com/android/contacts/common/list/ContactListFilterView.java b/src/com/android/contacts/common/list/ContactListFilterView.java index 4cea7558..075f1fb6 100644 --- a/src/com/android/contacts/common/list/ContactListFilterView.java +++ b/src/com/android/contacts/common/list/ContactListFilterView.java @@ -28,6 +28,8 @@ import android.widget.TextView; import com.android.contacts.common.R; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.account.AccountType; +import com.android.contacts.common.model.account.PhoneAccountType; +import com.android.contacts.common.model.account.SimAccountType; /** * Contact list filter parameters. @@ -113,7 +115,13 @@ public class ContactListFilterView extends LinearLayout { break; } case ContactListFilter.FILTER_TYPE_ACCOUNT: { - mAccountUserName.setVisibility(View.VISIBLE); + if (SimAccountType.ACCOUNT_TYPE.equals(mFilter.accountType) + || PhoneAccountType.ACCOUNT_TYPE + .equals(mFilter.accountType)) { + mAccountUserName.setVisibility(View.GONE); + } else { + mAccountUserName.setVisibility(View.VISIBLE); + } mIcon.setVisibility(View.VISIBLE); if (mFilter.icon != null) { mIcon.setImageDrawable(mFilter.icon); @@ -123,7 +131,7 @@ public class ContactListFilterView extends LinearLayout { final AccountType accountType = accountTypes.getAccountType(mFilter.accountType, mFilter.dataSet); mAccountUserName.setText(mFilter.accountName); - mAccountType.setText(accountType.getDisplayLabel(getContext())); + mAccountType.setText(accountType.getDisplayLabel(getContext(),mFilter.accountName)); break; } } diff --git a/src/com/android/contacts/common/list/ContactTileAdapter.java b/src/com/android/contacts/common/list/ContactTileAdapter.java index ac10f383..48c6923f 100644 --- a/src/com/android/contacts/common/list/ContactTileAdapter.java +++ b/src/com/android/contacts/common/list/ContactTileAdapter.java @@ -15,6 +15,7 @@ */ package com.android.contacts.common.list; +import android.accounts.Account; import android.content.ContentUris; import android.content.Context; import android.content.res.Resources; @@ -67,6 +68,8 @@ public class ContactTileAdapter extends BaseAdapter { protected int mNameIndex; protected int mPresenceIndex; protected int mStatusIndex; + private int mAccountTypeIndex; + private int mAccountNameIndex; private boolean mIsQuickContactEnabled = false; private final int mPaddingInPixels; @@ -149,6 +152,8 @@ public class ContactTileAdapter extends BaseAdapter { mStarredIndex = ContactTileLoaderFactory.STARRED; mPresenceIndex = ContactTileLoaderFactory.CONTACT_PRESENCE; mStatusIndex = ContactTileLoaderFactory.CONTACT_STATUS; + mAccountTypeIndex = ContactTileLoaderFactory.ACCOUNT_TYPE; + mAccountNameIndex = ContactTileLoaderFactory.ACCOUNT_NAME; } private static boolean cursorIsValid(Cursor cursor) { @@ -187,6 +192,9 @@ public class ContactTileAdapter extends BaseAdapter { * Else use {@link ContactTileLoaderFactory} */ public void setContactCursor(Cursor cursor) { + if (cursor == null || cursor.isClosed()) { + return; + } mContactCursor = cursor; mDividerPosition = getDividerPosition(cursor); @@ -271,6 +279,13 @@ public class ContactTileAdapter extends BaseAdapter { } contact.status = statusMessage; + if (!cursor.isNull(mAccountTypeIndex) && !cursor.isNull(mAccountTypeIndex)) { + final String accountType = cursor.getString(mAccountTypeIndex); + final String accountName = cursor.getString(mAccountNameIndex); + contact.account = new Account(accountName, accountType); + } else { + contact.account = null; + } return contact; } diff --git a/src/com/android/contacts/common/list/ContactTileView.java b/src/com/android/contacts/common/list/ContactTileView.java index 56552bb6..b42ab7bf 100644 --- a/src/com/android/contacts/common/list/ContactTileView.java +++ b/src/com/android/contacts/common/list/ContactTileView.java @@ -15,6 +15,7 @@ */ package com.android.contacts.common.list; +import android.accounts.Account; import android.content.Context; import android.graphics.Rect; import android.net.Uri; @@ -128,7 +129,8 @@ public abstract class ContactTileView extends FrameLayout { DefaultImageRequest request = getDefaultImageRequest(entry.name, entry.lookupKey); configureViewForImage(entry.photoUri == null); if (mPhoto != null) { - mPhotoManager.loadPhoto(mPhoto, entry.photoUri, getApproximateImageSize(), + mPhotoManager.loadPhoto(mPhoto, entry.photoUri, + entry.account, getApproximateImageSize(), isDarkTheme(), isContactPhotoCircular(), request); if (mQuickContact != null) { @@ -136,7 +138,7 @@ public abstract class ContactTileView extends FrameLayout { } } else if (mQuickContact != null) { mQuickContact.assignContactUri(mLookupUri); - mPhotoManager.loadPhoto(mQuickContact, entry.photoUri, + mPhotoManager.loadPhoto(mQuickContact, entry.photoUri, entry.account, getApproximateImageSize(), isDarkTheme(), isContactPhotoCircular(), request); } diff --git a/src/com/android/contacts/common/list/CustomContactListFilterActivity.java b/src/com/android/contacts/common/list/CustomContactListFilterActivity.java index b3253cc3..4066926a 100644 --- a/src/com/android/contacts/common/list/CustomContactListFilterActivity.java +++ b/src/com/android/contacts/common/list/CustomContactListFilterActivity.java @@ -39,6 +39,7 @@ import android.preference.PreferenceManager; import android.provider.ContactsContract; import android.provider.ContactsContract.Groups; import android.provider.ContactsContract.Settings; +import android.text.TextUtils; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -54,11 +55,14 @@ import android.widget.ExpandableListView.ExpandableListContextMenuInfo; import android.widget.TextView; import com.android.contacts.common.R; +import com.android.contacts.common.MoreContactUtils; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.ValuesDelta; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contacts.common.model.account.GoogleAccountType; +import com.android.contacts.common.model.account.PhoneAccountType; +import com.android.contacts.common.model.account.SimAccountType; import com.android.contacts.common.util.EmptyService; import com.android.contacts.common.util.LocalizedNameResolver; import com.android.contacts.common.util.WeakAsyncTask; @@ -349,7 +353,11 @@ public class CustomContactListFilterActivity extends Activity final Integer titleRes = getAsInteger(Groups.TITLE_RES); if (titleRes != null) { final String packageName = getAsString(Groups.RES_PACKAGE); - return context.getPackageManager().getText(packageName, titleRes, null); + if (!TextUtils.isEmpty(packageName)) { + return context.getPackageManager().getText(packageName, titleRes, null); + } else { + return getAsString(Groups.TITLE); + } } else { return getAsString(Groups.TITLE); } @@ -584,11 +592,17 @@ public class CustomContactListFilterActivity extends Activity final AccountType accountType = mAccountTypes.getAccountType( account.mType, account.mDataSet); - - text1.setText(account.mName); - text1.setVisibility(account.mName == null ? View.GONE : View.VISIBLE); - text2.setText(accountType.getDisplayLabel(mContext)); - + if (SimAccountType.ACCOUNT_TYPE.equals(account.mType) + || PhoneAccountType.ACCOUNT_TYPE.equals(account.mType)) { + text1.setVisibility(View.VISIBLE); + text1.setText(accountType.getDisplayLabel(mContext, account.mName)); + text2.setVisibility(View.GONE); + } else { + text1.setText(account.mName); + text1.setVisibility(account.mName == null ? View.GONE : View.VISIBLE); + text2.setText(accountType.getDisplayLabel(mContext, account.mName)); + text2.setVisibility(View.VISIBLE); + } return convertView; } diff --git a/src/com/android/contacts/common/list/DefaultContactListAdapter.java b/src/com/android/contacts/common/list/DefaultContactListAdapter.java index 8774777c..f25f5d7f 100644 --- a/src/com/android/contacts/common/list/DefaultContactListAdapter.java +++ b/src/com/android/contacts/common/list/DefaultContactListAdapter.java @@ -26,11 +26,15 @@ import android.preference.PreferenceManager; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; 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.common.model.account.SimAccountType; import com.android.contacts.common.preference.ContactsPreferences; +import com.android.contacts.common.MoreContactUtils; +import com.android.contacts.common.SimContactsConstants; import java.util.ArrayList; import java.util.List; @@ -47,6 +51,21 @@ public class DefaultContactListAdapter extends ContactListAdapter { super(context); } + /** append Uri QueryParameter to filter contacts in SIM card */ + private void appendUriQueryParameterWithoutSim(CursorLoader loader, + String key, String value) { + if (null == loader || null == key || null == value) { + return; + } + + Uri uri = loader.getUri(); + if (null != uri) { + uri = uri.buildUpon().appendQueryParameter(key, value) + .appendQueryParameter(SimContactsConstants.WITHOUT_SIM_FLAG, "true").build(); + loader.setUri(uri); + } + } + @Override public void configureLoader(CursorLoader loader, long directoryId) { if (loader instanceof ProfileAndContactsLoader) { @@ -79,6 +98,21 @@ public class DefaultContactListAdapter extends ContactListAdapter { loader.setUri(builder.build()); loader.setProjection(getProjection(true)); } + boolean isAirMode = MoreContactUtils.isAPMOnAndSIMPowerDown(getContext()); + + if (isAirMode + || (null != filter && filter.filterType == + ContactListFilter.FILTER_TYPE_ALL_WITHOUT_SIM)) { + appendUriQueryParameterWithoutSim(loader, + RawContacts.ACCOUNT_TYPE, SimAccountType.ACCOUNT_TYPE); + } else { + // Do not show contacts when SIM card is disabled + String disabledSimFilter = MoreContactUtils.getDisabledSimFilter(); + if (!TextUtils.isEmpty(disabledSimFilter)) { + appendUriQueryParameterWithoutSim( + loader, RawContacts.ACCOUNT_NAME, disabledSimFilter); + } + } } else { configureUri(loader, directoryId, filter); loader.setProjection(getProjection(false)); @@ -139,10 +173,21 @@ public class DefaultContactListAdapter extends ContactListAdapter { StringBuilder selection = new StringBuilder(); List<String> selectionArgs = new ArrayList<String>(); + boolean isAirMode = MoreContactUtils.isAPMOnAndSIMPowerDown(getContext()); + String disabledSimFilter = MoreContactUtils.getDisabledSimFilter(); + switch (filter.filterType) { case ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS: { // We have already added directory=0 to the URI, which takes care of this // filter + // Do not show contacts in SIM card when airplane mode is on + if (isAirMode) { + appendUriQueryParameterWithoutSim(loader, RawContacts.ACCOUNT_TYPE, + SimAccountType.ACCOUNT_TYPE); + } else if (!TextUtils.isEmpty(disabledSimFilter)) { + appendUriQueryParameterWithoutSim(loader, RawContacts.ACCOUNT_NAME, + disabledSimFilter); + } break; } case ContactListFilter.FILTER_TYPE_SINGLE_CONTACT: { @@ -163,12 +208,25 @@ public class DefaultContactListAdapter extends ContactListAdapter { if (isCustomFilterForPhoneNumbersOnly()) { selection.append(" AND " + Contacts.HAS_PHONE_NUMBER + "=1"); } + // Do not show contacts in SIM card when airplane mode is on + if (isAirMode) { + appendUriQueryParameterWithoutSim(loader, RawContacts.ACCOUNT_TYPE, + SimAccountType.ACCOUNT_TYPE); + } else if (!TextUtils.isEmpty(disabledSimFilter)) { + appendUriQueryParameterWithoutSim(loader, RawContacts.ACCOUNT_NAME, + disabledSimFilter); + } break; } case ContactListFilter.FILTER_TYPE_ACCOUNT: { // We use query parameters for account filter, so no selection to add here. break; } + case ContactListFilter.FILTER_TYPE_ALL_WITHOUT_SIM: { + appendUriQueryParameterWithoutSim(loader, RawContacts.ACCOUNT_TYPE, + SimAccountType.ACCOUNT_TYPE); + break; + } } loader.setSelection(selection.toString()); loader.setSelectionArgs(selectionArgs.toArray(new String[0])); @@ -190,7 +248,8 @@ public class DefaultContactListAdapter extends ContactListAdapter { 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/common/list/PhoneNumberListAdapter.java b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java index fee0a6a1..750f030e 100644 --- a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java +++ b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java @@ -15,6 +15,7 @@ */ package com.android.contacts.common.list; +import android.accounts.Account; import android.content.ContentUris; import android.content.Context; import android.content.CursorLoader; @@ -28,6 +29,8 @@ import android.provider.ContactsContract.CommonDataKinds.SipAddress; 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.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.util.Log; @@ -41,6 +44,8 @@ import com.android.contacts.common.extensions.ExtendedPhoneDirectoriesManager; import com.android.contacts.common.extensions.ExtensionsFactory; import com.android.contacts.common.preference.ContactsPreferences; import com.android.contacts.common.util.Constants; +import com.android.contacts.common.MoreContactUtils; +import com.android.contacts.common.model.account.SimAccountType; import java.util.ArrayList; import java.util.List; @@ -95,6 +100,8 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { 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[] { @@ -107,6 +114,8 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { 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; @@ -118,6 +127,8 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { 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 PHONE_ACCOUNT_TYPE = 9; + public static final int PHONE_ACCOUNT_NAME = 10; } private static final String IGNORE_NUMBER_TOO_LONG_CLAUSE = @@ -212,6 +223,13 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { // Remove duplicates when it is possible. builder.appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true"); + + // Do not show contacts in disabled SIM card + String disabledSimFilter = MoreContactUtils.getDisabledSimFilter(); + if (!TextUtils.isEmpty(disabledSimFilter)) { + String disabledSimName = getDisabledSimName(disabledSimFilter); + loader.setSelection(RawContacts.ACCOUNT_NAME+ "<>" + disabledSimName); + } loader.setUri(builder.build()); // TODO a projection that includes the search snippet @@ -396,7 +414,8 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { 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.PHONE_ACCOUNT_TYPE, PhoneQuery.PHONE_ACCOUNT_NAME); } else { if (getDisplayPhotos()) { bindPhoto(view, partition, cursor); @@ -466,9 +485,15 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { if (!cursor.isNull(PhoneQuery.PHOTO_ID)) { photoId = cursor.getLong(PhoneQuery.PHOTO_ID); } - + Account account = null; + if (!cursor.isNull(PhoneQuery.PHONE_ACCOUNT_TYPE) + && !cursor.isNull(PhoneQuery.PHONE_ACCOUNT_NAME)) { + final String accountType = cursor.getString(PhoneQuery.PHONE_ACCOUNT_TYPE); + final String accountName = cursor.getString(PhoneQuery.PHONE_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); @@ -480,7 +505,7 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { 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); } } @@ -571,4 +596,21 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { .encodedFragment(cursor.getString(lookUpKeyColumn)) .build(); } + + private String getDisabledSimName(String disabledSimFilter){ + String[] disabledSimArray = disabledSimFilter.split(",");//it will never be null + String disabledSimName = ""; + for (int i = 0; i < disabledSimArray.length; i++) { + if (i < disabledSimArray.length -1) { + //If disabledSimArray[i] is not the last one of the array, + //add "or" after every member of the array. + disabledSimName = disabledSimName + "'" + disabledSimArray[i] + "'" + "or"; + } else { + //If disabledSimArray[i] is the last one of the array, + //should not add anything after it. + disabledSimName = disabledSimName + "'" + disabledSimArray[i] + "'"; + } + } + return disabledSimName; + } } diff --git a/src/com/android/contacts/common/list/ShortcutIntentBuilder.java b/src/com/android/contacts/common/list/ShortcutIntentBuilder.java index 9e6b5e95..35c3ff21 100644 --- a/src/com/android/contacts/common/list/ShortcutIntentBuilder.java +++ b/src/com/android/contacts/common/list/ShortcutIntentBuilder.java @@ -15,6 +15,7 @@ */ package com.android.contacts.common.list; +import android.accounts.Account; import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; @@ -39,6 +40,7 @@ import android.provider.ContactsContract.Data; import android.support.v4.graphics.drawable.RoundedBitmapDrawable; import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; import android.telecom.PhoneAccount; +import android.provider.ContactsContract.RawContacts; import android.text.TextPaint; import android.text.TextUtils; import android.text.TextUtils.TruncateAt; @@ -56,12 +58,16 @@ public class ShortcutIntentBuilder { 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, @@ -69,7 +75,9 @@ public class ShortcutIntentBuilder { 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; @@ -78,6 +86,8 @@ public class ShortcutIntentBuilder { 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, @@ -187,6 +197,8 @@ public class ShortcutIntentBuilder { } private final class ContactLoadingAsyncTask extends LoadingAsyncTask { + private Account mAccount; + public ContactLoadingAsyncTask(Uri uri) { super(uri); } @@ -200,6 +212,13 @@ public class ShortcutIntentBuilder { if (cursor.moveToFirst()) { mDisplayName = cursor.getString(CONTACT_DISPLAY_NAME_COLUMN_INDEX); mPhotoId = cursor.getLong(CONTACT_PHOTO_ID_COLUMN_INDEX); + + final String accountType = cursor + .getString(CONTACT_ACCOUNT_TYPE_COLUMN_INDEX); + final String accountName = cursor + .getString(CONTACT_ACCOUNT_NAME_COLUMN_INDEX); + mAccount = new Account(accountName, accountType); + mLookupKey = cursor.getString(CONTACT_LOOKUP_KEY_COLUMN_INDEX); } } finally { @@ -209,7 +228,8 @@ public class ShortcutIntentBuilder { } @Override protected void onPostExecute(Void result) { - createContactShortcutIntent(mUri, mContentType, mDisplayName, mLookupKey, mBitmapData); + createContactShortcutIntent(mUri, mContentType, mDisplayName, + mAccount, mLookupKey, mBitmapData); } } @@ -218,6 +238,7 @@ public class ShortcutIntentBuilder { private String mPhoneNumber; private int mPhoneType; private String mPhoneLabel; + private Account mAccount; public PhoneNumberLoadingAsyncTask(Uri uri, String shortcutAction) { super(uri); @@ -236,6 +257,13 @@ public class ShortcutIntentBuilder { mPhoneNumber = cursor.getString(PHONE_NUMBER_COLUMN_INDEX); mPhoneType = cursor.getInt(PHONE_TYPE_COLUMN_INDEX); mPhoneLabel = cursor.getString(PHONE_LABEL_COLUMN_INDEX); + + final String accountType = cursor + .getString(PHONE_ACCOUNT_TYPE_COLUMN_INDEX); + final String accountName = cursor + .getString(PHONE_ACCOUNT_NAME_COLUMN_INDEX); + mAccount = new Account(accountName, accountType); + mLookupKey = cursor.getString(PHONE_LOOKUP_KEY_COLUMN_INDEX); } } finally { @@ -247,23 +275,25 @@ public class ShortcutIntentBuilder { @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); } else { - return ContactPhotoManager.getDefaultAvatarDrawableForContact(mContext.getResources(), - false, new DefaultImageRequest(displayName, lookupKey, false)); + return ContactPhotoManager.getDefaultAvatarDrawableForContact(mContext, + mContext.getResources(), false, + new DefaultImageRequest(displayName, lookupKey, false), account); } } private void createContactShortcutIntent(Uri contactUri, String contentType, String displayName, - String lookupKey, byte[] bitmapData) { - Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey); + Account account, String lookupKey, byte[] bitmapData) { + Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey, account); // Use an implicit intent without a package name set. It is reasonable for a disambiguation // dialog to appear when opening QuickContacts from the launcher. Plus, this will be more @@ -303,8 +333,8 @@ public class ShortcutIntentBuilder { private void createPhoneNumberShortcutIntent(Uri uri, String displayName, String lookupKey, byte[] bitmapData, String phoneNumber, int phoneType, String phoneLabel, - String shortcutAction) { - Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey); + Account account, String shortcutAction) { + Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey, account); Bitmap bitmap; Uri phoneUri; diff --git a/src/com/android/contacts/common/model/AccountTypeManager.java b/src/com/android/contacts/common/model/AccountTypeManager.java index 33395979..ca144d12 100644 --- a/src/com/android/contacts/common/model/AccountTypeManager.java +++ b/src/com/android/contacts/common/model/AccountTypeManager.java @@ -38,6 +38,8 @@ import android.os.Message; import android.os.SystemClock; import android.provider.ContactsContract; import android.text.TextUtils; +import android.telephony.TelephonyManager; +import android.telephony.SubscriptionManager; import android.util.Log; import android.util.TimingLogger; @@ -51,7 +53,10 @@ import com.android.contacts.common.model.account.ExternalAccountType; import com.android.contacts.common.model.account.FallbackAccountType; import com.android.contacts.common.model.account.GoogleAccountType; import com.android.contacts.common.model.account.SamsungAccountType; +import com.android.contacts.common.model.account.PhoneAccountType; +import com.android.contacts.common.model.account.SimAccountType; import com.android.contacts.common.model.dataitem.DataKind; +import com.android.contacts.common.SimContactsConstants; import com.android.contacts.common.testing.NeededForTesting; import com.android.contacts.common.util.Constants; import com.google.common.annotations.VisibleForTesting; @@ -80,6 +85,13 @@ public abstract class AccountTypeManager { private static final Object mInitializationLock = new Object(); private static AccountTypeManager mAccountTypeManager; + public static final int FLAG_ALL_ACCOUNTS = 0; + public static final int FLAG_ALL_ACCOUNTS_WITHOUT_SIM = 1; + /** + * without sim and phone accounts + */ + public static final int FLAG_ALL_ACCOUNTS_WITHOUT_LOCAL = 2; + /** * Requests the singleton instance of {@link AccountTypeManager} with data bound from * the available authenticators. This method can safely be called from the UI thread. @@ -113,6 +125,8 @@ public abstract class AccountTypeManager { * contact writable accounts (if contactWritableOnly is true). */ // TODO: Consider splitting this into getContactWritableAccounts() and getAllAccounts() + public abstract List<AccountWithDataSet> getAccounts(boolean contactWritableOnly, int flag); + public abstract List<AccountWithDataSet> getAccounts(boolean contactWritableOnly); /** @@ -417,6 +431,10 @@ class AccountTypeManagerImpl extends AccountTypeManager } else if (SamsungAccountType.isSamsungAccountType(mContext, type, auth.packageName)) { accountType = new SamsungAccountType(mContext, auth.packageName, type); + } else if (SimAccountType.ACCOUNT_TYPE.equals(type)) { + accountType = new SimAccountType(mContext, auth.packageName); + } else if (PhoneAccountType.ACCOUNT_TYPE.equals(type)) { + accountType = new PhoneAccountType(mContext, auth.packageName); } else { Log.d(TAG, "Registering external account type=" + type + ", packageName=" + auth.packageName); @@ -562,15 +580,86 @@ class AccountTypeManagerImpl extends AccountTypeManager return null; } + @Override + public List<AccountWithDataSet> getAccounts(boolean contactWritableOnly) { + return getAccounts(contactWritableOnly, FLAG_ALL_ACCOUNTS); + } + /** * Return list of all known, contact writable {@link AccountWithDataSet}'s. */ @Override - public List<AccountWithDataSet> getAccounts(boolean contactWritableOnly) { + public List<AccountWithDataSet> getAccounts(boolean contactWritableOnly, + int flag) { ensureAccountsLoaded(); + boolean isAirMode = MoreContactUtils.isAPMOnAndSIMPowerDown(mContext); + switch (flag) { + case FLAG_ALL_ACCOUNTS: + return trimAccountByType( + contactWritableOnly ? mContactWritableAccounts : mAccounts, + isAirMode ? SimAccountType.ACCOUNT_TYPE : null); + case FLAG_ALL_ACCOUNTS_WITHOUT_LOCAL: + return trimAccountByType( + contactWritableOnly ? mContactWritableAccounts : mAccounts, + SimAccountType.ACCOUNT_TYPE, PhoneAccountType.ACCOUNT_TYPE); + case FLAG_ALL_ACCOUNTS_WITHOUT_SIM: + return trimAccountByType( + contactWritableOnly ? mContactWritableAccounts : mAccounts, + SimAccountType.ACCOUNT_TYPE); + } return contactWritableOnly ? mContactWritableAccounts : mAccounts; } + private boolean isSimStateUnknown(AccountWithDataSet account) { + int subscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + subscription = MoreContactUtils.getSubscription(account.type, account.name); + + if (subscription > SubscriptionManager.INVALID_SUBSCRIPTION_ID + && TelephonyManager.from(mContext).getSimState(subscription) + == TelephonyManager.SIM_STATE_UNKNOWN) { + return true; + } else { + return false; + } + } + + private boolean isSimAccountInvalid(AccountWithDataSet account) { + if ((SimContactsConstants.ACCOUNT_TYPE_SIM).equals(account.type)) { + if (TelephonyManager.from(mContext).isMultiSimEnabled()) { + if (account.name.equals(SimContactsConstants.SIM_NAME)) + return true; + } else { + if (!account.name.equals(SimContactsConstants.SIM_NAME)) { + return true; + } + } + } + return false; + } + + private List<AccountWithDataSet> trimAccountByType(final List<AccountWithDataSet> list, + String... trimAccountTypes) { + List<AccountWithDataSet> tempList = Lists.newArrayList(); + outer: for (AccountWithDataSet accountWithDataSet : list) { + if (trimAccountTypes != null && trimAccountTypes.length > 0) { + for (String type : trimAccountTypes) { + if (type != null && type.equals(accountWithDataSet.type)) { + continue outer; + } + } + } + + if (isSimStateUnknown(accountWithDataSet)) { + continue outer; + } + if (isSimAccountInvalid(accountWithDataSet)) { + continue outer; + } + tempList.add(accountWithDataSet); + } + return tempList; + } + /** * Return the list of all known, group writable {@link AccountWithDataSet}'s. */ diff --git a/src/com/android/contacts/common/model/RawContactDelta.java b/src/com/android/contacts/common/model/RawContactDelta.java index 7304f023..435e3442 100644 --- a/src/com/android/contacts/common/model/RawContactDelta.java +++ b/src/com/android/contacts/common/model/RawContactDelta.java @@ -24,15 +24,20 @@ import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.provider.BaseColumns; +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.Profile; import android.provider.ContactsContract.RawContacts; +import android.text.TextUtils; import android.util.Log; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.ValuesDelta; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.testing.NeededForTesting; +import com.android.contacts.common.SimContactsConstants; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -416,6 +421,139 @@ public class RawContactDelta implements Parcelable { } } + 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 (getMimeEntriesCount(StructuredName.CONTENT_ITEM_TYPE, true) > 0) { + nameValuesDelta = names.get(0); + names.get(0).putNull(StructuredName.GIVEN_NAME); + names.get(0).putNull(StructuredName.FAMILY_NAME); + names.get(0).putNull(StructuredName.PREFIX); + names.get(0).putNull(StructuredName.MIDDLE_NAME); + names.get(0).putNull(StructuredName.SUFFIX); + names.get(0).put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); + } + 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.DISPLAY_NAME); + } else { + if (nameValuesDelta.getBefore() != null) { + name = nameValuesDelta.getBefore() + .getAsString(StructuredName.DISPLAY_NAME); + } + if (nameValuesDelta.getAfter() != null) { + newName = nameValuesDelta.getAfter() + .getAsString(StructuredName.DISPLAY_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)) { + if (TextUtils.isEmpty(number)) { + number = valuesDelta.getAfter().getAsString(Phone.NUMBER); + } else { + // don't supports to save two mobile number,so gives + // invalid str here + number = SimContactsConstants.STR_ANRS; + } + } 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)) { + if (TextUtils.isEmpty(newNumber)) { + newNumber = valuesDelta.getAfter().getAsString(Phone.NUMBER); + } else { + newNumber = SimContactsConstants.STR_ANRS; + } + } 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()) { + if (name != null || number != null || anr != null || email != null) { + 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/common/model/ValuesDelta.java b/src/com/android/contacts/common/model/ValuesDelta.java index 36321d24..5dd95dd9 100644 --- a/src/com/android/contacts/common/model/ValuesDelta.java +++ b/src/com/android/contacts/common/model/ValuesDelta.java @@ -23,6 +23,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.provider.BaseColumns; import android.provider.ContactsContract; +import android.provider.ContactsContract.Data; import com.android.contacts.common.testing.NeededForTesting; import com.google.common.collect.Sets; @@ -61,6 +62,17 @@ public class ValuesDelta implements Parcelable { final ValuesDelta entry = new ValuesDelta(); entry.mBefore = before; entry.mAfter = new ContentValues(); + + // init data1 to mAfter map. when no operation edittext of + // sim phone in the UI, the mAfter init have no data1 value, + // it will cause the builddiff data not right. + if (before.containsKey(Data.DATA1)) { + String contactInfo = before.getAsString(Data.DATA1); + if (null != contactInfo && !"".equals(contactInfo)) { + entry.mAfter.put(Data.DATA1, contactInfo); + } + } + return entry; } @@ -83,6 +95,10 @@ public class ValuesDelta implements Parcelable { return mAfter; } + public ContentValues getBefore() { + return mBefore; + } + public boolean containsKey(String key) { return ((mAfter != null && mAfter.containsKey(key)) || (mBefore != null && mBefore.containsKey(key))); diff --git a/src/com/android/contacts/common/model/account/AccountType.java b/src/com/android/contacts/common/model/account/AccountType.java index 53ab47d9..ae4706ff 100644..100755 --- a/src/com/android/contacts/common/model/account/AccountType.java +++ b/src/com/android/contacts/common/model/account/AccountType.java @@ -16,19 +16,26 @@ package com.android.contacts.common.model.account; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorDescription; import android.content.ContentValues; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.RawContacts; +import android.telephony.TelephonyManager; +import android.util.Log; import android.view.inputmethod.EditorInfo; import android.widget.EditText; +import com.android.contacts.common.MoreContactUtils; import com.android.contacts.common.R; import com.android.contacts.common.model.dataitem.DataKind; +import com.android.internal.telephony.PhoneConstants; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -39,6 +46,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Internal structure that represents constraints and styles for a specific data @@ -96,6 +104,11 @@ public abstract class AccountType { protected boolean mIsInitialized; + private Map<String, AuthenticatorDescription> mTypeToAuthDescription + = new HashMap<String, AuthenticatorDescription>(); + + private AuthenticatorDescription[] mAuthDescs; + protected static class DefinitionException extends Exception { public DefinitionException(String message) { super(message); @@ -196,12 +209,6 @@ public abstract class AccountType { public String getViewGroupActivity() { return null; } - - public CharSequence getDisplayLabel(Context context) { - // Note this resource is defined in the sync adapter package, not resourcePackageName. - return getResourceText(context, syncAdapterPackageName, titleRes, accountType); - } - /** * @return resource ID for the "invite contact" action label, or -1 if not defined. */ @@ -276,15 +283,89 @@ public abstract class AccountType { } } + public void updateAuthDescriptions(Context context) { + mAuthDescs = AccountManager.get(context).getAuthenticatorTypes(); + for (int i = 0; i < mAuthDescs.length; i++) { + mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]); + } + } + public CharSequence getDisplayLabel(Context context) { + CharSequence label = null; + updateAuthDescriptions(context); + if (mTypeToAuthDescription.containsKey(accountType)) { + try { + AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + Context authContext = context.createPackageContext(desc.packageName, 0); + label = authContext.getResources().getText(desc.labelId); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "No label name for account type " + accountType); + } catch (Resources.NotFoundException e) { + Log.w(TAG, "No label icon for account type " + accountType); + } + } + return label; + } + + public CharSequence getDisplayLabel(Context context, String accountName) { + if ((SimAccountType.ACCOUNT_TYPE).equals(accountType)) { + final int slot = MoreContactUtils.getSubscription(accountType, + accountName); + return MoreContactUtils.getMultiSimAliasesName(context, slot); + } + return getDisplayLabel(context); + } + + /** + * Gets an icon associated with a particular account type. If none found, return null. + * + * @param accountType the type of account + * @return a drawable for the icon or null if one cannot be found. + */ public Drawable getDisplayIcon(Context context) { - if (this.titleRes != -1 && this.syncAdapterPackageName != null) { - final PackageManager pm = context.getPackageManager(); - return pm.getDrawable(this.syncAdapterPackageName, this.iconRes, null); - } else if (this.titleRes != -1) { - return context.getResources().getDrawable(this.iconRes); - } else { - return null; + Drawable icon = null; + updateAuthDescriptions(context); + if (PhoneAccountType.ACCOUNT_TYPE.equals(accountType)) { + return context.getResources().getDrawable(R.drawable.phone_account); + } + if (mTypeToAuthDescription.containsKey(accountType)) { + try { + AuthenticatorDescription desc = mTypeToAuthDescription + .get(accountType); + Context authContext = context.createPackageContext( + desc.packageName, 0); + icon = authContext.getResources().getDrawable(desc.iconId); + } catch (PackageManager.NameNotFoundException e) { + } catch (Resources.NotFoundException e) { + } + } + if (icon == null) { + icon = context.getPackageManager().getDefaultActivityIcon(); + } + return icon; + } + + public Drawable getDisplayIcon(Context context, String accountName) { + if ((SimAccountType.ACCOUNT_TYPE).equals(accountType)) { + final int slot = MoreContactUtils.getSubscription(accountType, + accountName); + if (TelephonyManager.getDefault().isMultiSimEnabled()) { + switch (slot) { + case PhoneConstants.SUB1: + return context.getResources().getDrawable( + R.drawable.sim1_account); + case PhoneConstants.SUB2: + return context.getResources().getDrawable( + R.drawable.sim2_account); + default: + return context.getResources().getDrawable( + R.drawable.simcard_account); + } + } else { + return context.getResources().getDrawable( + R.drawable.simcard_account); + } } + return getDisplayIcon(context); } /** diff --git a/src/com/android/contacts/common/model/account/PhoneAccountType.java b/src/com/android/contacts/common/model/account/PhoneAccountType.java new file mode 100644 index 00000000..d54216fd --- /dev/null +++ b/src/com/android/contacts/common/model/account/PhoneAccountType.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015, 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.common.model.account; + +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.view.inputmethod.EditorInfo; + +import com.android.contacts.common.R; +import com.android.contacts.common.SimContactsConstants; +import com.android.contacts.common.model.account.AccountType.DefinitionException; + + +public class PhoneAccountType extends BaseAccountType{ + private static final String TAG = "PhoneAccountType"; + + public static final String ACCOUNT_TYPE = SimContactsConstants.ACCOUNT_TYPE_PHONE; + public static final int FLAGS_PERSON_NAME = EditorInfo.TYPE_CLASS_TEXT + | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME; + protected static final int FLAGS_PHONE = EditorInfo.TYPE_CLASS_PHONE; + + public PhoneAccountType(Context context, String resPackageName) { + this.accountType = ACCOUNT_TYPE; + this.resourcePackageName = null; + this.syncAdapterPackageName = resPackageName; + + try { + + addDataKindStructuredName(context); + addDataKindDisplayName(context); + addDataKindPhoneticName(context); + addDataKindNickname(context); + addDataKindPhone(context); + addDataKindEmail(context); + addDataKindStructuredPostal(context); + addDataKindIm(context); + addDataKindOrganization(context); + addDataKindPhoto(context); + addDataKindNote(context); + addDataKindWebsite(context); + addDataKindGroupMembership(context); + if (context.getResources().getBoolean( + com.android.internal.R.bool.config_built_in_sip_phone)) { + addDataKindSipAddress(context); + } + + mIsInitialized = true; + } catch (DefinitionException e) { + Log.e(TAG, "Problem building account type", e); + } + + } + + @Override + public boolean isGroupMembershipEditable() { + return true; + } + + @Override + public boolean areContactsWritable() { + return true; + } + +} diff --git a/src/com/android/contacts/common/model/account/SimAccountType.java b/src/com/android/contacts/common/model/account/SimAccountType.java new file mode 100644 index 00000000..ba74be7c --- /dev/null +++ b/src/com/android/contacts/common/model/account/SimAccountType.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2015, 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.common.model.account; + +import android.content.Context; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.util.Log; +import android.view.inputmethod.EditorInfo; +import android.graphics.drawable.Drawable; + +import com.android.contacts.common.R; +import com.android.contacts.common.SimContactsConstants; +import com.android.contacts.common.model.account.AccountType.DefinitionException; +import com.android.contacts.common.model.dataitem.DataKind; +import com.google.android.collect.Lists; + +public class SimAccountType extends BaseAccountType{ + private static final String TAG = "SimContactsType"; + + public static final String ACCOUNT_TYPE = SimContactsConstants.ACCOUNT_TYPE_SIM; + public static final int FLAGS_PERSON_NAME = EditorInfo.TYPE_CLASS_TEXT + | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME; + public static final int FLAGS_PHONETIC = EditorInfo.TYPE_CLASS_TEXT + | EditorInfo.TYPE_TEXT_VARIATION_PHONETIC; + protected static final int FLAGS_PHONE = EditorInfo.TYPE_CLASS_PHONE; + private static Context mContext; + + public SimAccountType(Context context, String resPackageName) { + this.accountType = ACCOUNT_TYPE; + this.resourcePackageName = resPackageName; + this.syncAdapterPackageName = resPackageName; + + this.mContext = context; + + try { + addDataKindStructuredName(context); + addDataKindDisplayName(context); + addDataKindPhone(context); + addDataKindEmail(context); + mIsInitialized = true; + } catch (DefinitionException e) { + Log.e(TAG, "Problem building account type", e); + } + } + + @Override + protected DataKind addDataKindStructuredName(Context context) throws DefinitionException { + final DataKind kind = super.addDataKindStructuredName(context); + kind.fieldList = Lists.newArrayList(); + kind.fieldList.add(new EditField(StructuredName.DISPLAY_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.typeColumn = Email.TYPE; + kind.fieldList = Lists.newArrayList(); + kind.fieldList.add(new EditField(Email.ADDRESS, R.string.emailLabelsGroup, FLAGS_EMAIL)); + return kind; + } + + + @Override + public boolean isGroupMembershipEditable() { + return false; + } + + @Override + public boolean areContactsWritable() { + return true; + } + +} diff --git a/src/com/android/contacts/common/util/AccountSelectionUtil.java b/src/com/android/contacts/common/util/AccountSelectionUtil.java index 4302dc43..79eb44a8 100644 --- a/src/com/android/contacts/common/util/AccountSelectionUtil.java +++ b/src/com/android/contacts/common/util/AccountSelectionUtil.java @@ -22,6 +22,8 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; @@ -31,11 +33,14 @@ import android.widget.ArrayAdapter; import android.widget.TextView; import com.android.contacts.common.R; +import com.android.contacts.common.SimContactsConstants; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contacts.common.vcard.ImportVCardActivity; +import com.android.internal.telephony.PhoneConstants; +import java.util.ArrayList; import java.util.List; /** @@ -48,6 +53,8 @@ public class AccountSelectionUtil { public static boolean mVCardShare = false; public static Uri mPath; + // QRD enhancement: import subscription selected by user + private static int mImportSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID; public static class AccountSelectedListener implements DialogInterface.OnClickListener { @@ -56,7 +63,7 @@ public class AccountSelectionUtil { final private int mResId; final private int mSubscriptionId; - final protected List<AccountWithDataSet> mAccountList; + protected List<AccountWithDataSet> mAccountList; public AccountSelectedListener(Context context, List<AccountWithDataSet> accountList, int resId, int subscriptionId) { @@ -78,8 +85,21 @@ public class AccountSelectionUtil { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); - doImport(mContext, mResId, mAccountList.get(which), mSubscriptionId); + doImport(mContext, mResId, mAccountList.get(which)); } + /** + * Reset the account list for this listener, to make sure the selected + * items reflect the displayed items. + * + * @param accountList The reset account list. + */ + void setAccountList(List<AccountWithDataSet> accountList) { + mAccountList = accountList; + } + } + + public static void setImportSubscription(int subscription) { + mImportSub = subscription; } public static Dialog getSelectAccountDialog(Context context, int resId) { @@ -91,15 +111,28 @@ public class AccountSelectionUtil { return getSelectAccountDialog(context, resId, onClickListener, null); } + public static Dialog getSelectAccountDialog(Context context, int resId, + DialogInterface.OnClickListener onClickListener, + DialogInterface.OnCancelListener onCancelListener) { + return getSelectAccountDialog(context, resId, onClickListener, + onCancelListener, true); + } + /** * When OnClickListener or OnCancelListener is null, uses a default listener. * The default OnCancelListener just closes itself with {@link Dialog#dismiss()}. */ public static Dialog getSelectAccountDialog(Context context, int resId, DialogInterface.OnClickListener onClickListener, - DialogInterface.OnCancelListener onCancelListener) { + DialogInterface.OnCancelListener onCancelListener, boolean includeSIM) { final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context); - final List<AccountWithDataSet> writableAccountList = accountTypes.getAccounts(true); + List<AccountWithDataSet> writableAccountList = accountTypes.getAccounts(true); + if (includeSIM) { + writableAccountList = accountTypes.getAccounts(true); + } else { + writableAccountList = accountTypes.getAccounts(true, + AccountTypeManager.FLAG_ALL_ACCOUNTS_WITHOUT_SIM); + } Log.i(LOG_TAG, "The number of available accounts: " + writableAccountList.size()); @@ -144,6 +177,13 @@ public class AccountSelectionUtil { AccountSelectedListener accountSelectedListener = new AccountSelectedListener(context, writableAccountList, resId); onClickListener = accountSelectedListener; + } else if (onClickListener instanceof AccountSelectedListener) { + // Because the writableAccountList is different if includeSIM or not, so + // should reset the account list for the AccountSelectedListener which + // is initialized with FLAG_ALL_ACCOUNTS. + // Reset the account list to make sure the selected account is contained + // in these display accounts. + ((AccountSelectedListener) onClickListener).setAccountList(writableAccountList); } if (onCancelListener == null) { onCancelListener = new DialogInterface.OnCancelListener() { @@ -159,11 +199,11 @@ public class AccountSelectionUtil { .create(); } - public static void doImport(Context context, int resId, AccountWithDataSet account, - int subscriptionId) { + public static void doImport(Context context, int resId, + AccountWithDataSet account) { switch (resId) { - case R.string.manage_sim_contacts: { - doImportFromSim(context, account, subscriptionId); + case R.string.import_from_sim: { + doImportFromSim(context, account); break; } case R.string.import_from_vcf_file: { @@ -173,17 +213,18 @@ public class AccountSelectionUtil { } } - public static void doImportFromSim(Context context, AccountWithDataSet account, - int subscriptionId) { - Intent importIntent = new Intent(Intent.ACTION_VIEW); - importIntent.setType("vnd.android.cursor.item/sim-contact"); + public static void doImportFromSim(Context context, AccountWithDataSet account) { + Intent importIntent = new Intent(SimContactsConstants.ACTION_MULTI_PICK_SIM); if (account != null) { - importIntent.putExtra("account_name", account.name); - importIntent.putExtra("account_type", account.type); - importIntent.putExtra("data_set", account.dataSet); + importIntent.putExtra(SimContactsConstants.ACCOUNT_NAME, account.name); + importIntent.putExtra(SimContactsConstants.ACCOUNT_TYPE, account.type); + importIntent.putExtra(SimContactsConstants.ACCOUNT_DATA, account.dataSet); + } + if (TelephonyManager.getDefault().isMultiSimEnabled()) { + importIntent.putExtra(PhoneConstants.SLOT_KEY, mImportSub); + } else { + importIntent.putExtra(PhoneConstants.SLOT_KEY,PhoneConstants.SUB1); } - importIntent.putExtra("subscription_id", (Integer) subscriptionId); - importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts"); context.startActivity(importIntent); } diff --git a/src/com/android/contacts/common/util/AccountsListAdapter.java b/src/com/android/contacts/common/util/AccountsListAdapter.java index 84435df8..96a87b5d 100644 --- a/src/com/android/contacts/common/util/AccountsListAdapter.java +++ b/src/com/android/contacts/common/util/AccountsListAdapter.java @@ -29,6 +29,8 @@ import com.android.contacts.common.R; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.model.account.AccountWithDataSet; +import com.android.contacts.common.model.account.PhoneAccountType; +import com.android.contacts.common.model.account.SimAccountType; import java.util.ArrayList; import java.util.List; @@ -48,7 +50,8 @@ public final class AccountsListAdapter extends BaseAdapter { public enum AccountListFilter { ALL_ACCOUNTS, // All read-only and writable accounts ACCOUNTS_CONTACT_WRITABLE, // Only where the account type is contact writable - ACCOUNTS_GROUP_WRITABLE // Only accounts where the account type is group writable + ACCOUNTS_GROUP_WRITABLE, // Only accounts where the account type is group writable + ACCOUNTS_CONTACT_WRITABLE_WITHOUT_SIM } public AccountsListAdapter(Context context, AccountListFilter accountListFilter) { @@ -75,10 +78,22 @@ public final class AccountsListAdapter extends BaseAdapter { private List<AccountWithDataSet> getAccounts(AccountListFilter accountListFilter) { if (accountListFilter == AccountListFilter.ACCOUNTS_GROUP_WRITABLE) { - return new ArrayList<AccountWithDataSet>(mAccountTypes.getGroupWritableAccounts()); + return new ArrayList<AccountWithDataSet>(mAccountTypes.getAccounts(true, + AccountTypeManager.FLAG_ALL_ACCOUNTS_WITHOUT_SIM)); } - return new ArrayList<AccountWithDataSet>(mAccountTypes.getAccounts( - accountListFilter == AccountListFilter.ACCOUNTS_CONTACT_WRITABLE)); + final List<AccountWithDataSet> writableAccountList = mAccountTypes + .getAccounts(accountListFilter == AccountListFilter.ACCOUNTS_CONTACT_WRITABLE + || accountListFilter == AccountListFilter.ACCOUNTS_CONTACT_WRITABLE_WITHOUT_SIM); + List<AccountWithDataSet> deletedList = new ArrayList<AccountWithDataSet>(); + + if (accountListFilter == AccountListFilter.ACCOUNTS_CONTACT_WRITABLE_WITHOUT_SIM) { + for (AccountWithDataSet account : writableAccountList) { + if (SimAccountType.ACCOUNT_TYPE.equals(account.type)) + deletedList.add(account); + } + writableAccountList.removeAll(deletedList); + } + return writableAccountList; } @Override @@ -93,14 +108,20 @@ public final class AccountsListAdapter extends BaseAdapter { final AccountWithDataSet account = mAccounts.get(position); final AccountType accountType = mAccountTypes.getAccountType(account.type, account.dataSet); - text1.setText(accountType.getDisplayLabel(mContext)); + text1.setText(accountType.getDisplayLabel(mContext, account.name)); // For email addresses, we don't want to truncate at end, which might cut off the domain // name. + if (SimAccountType.ACCOUNT_TYPE.equals(account.type) + || PhoneAccountType.ACCOUNT_TYPE.equals(account.type)) { + text2.setVisibility(View.GONE); + } else { + text2.setVisibility(View.VISIBLE); + } text2.setText(account.name); text2.setEllipsize(TruncateAt.MIDDLE); - icon.setImageDrawable(accountType.getDisplayIcon(mContext)); + icon.setImageDrawable(accountType.getDisplayIcon(mContext, account.name)); return resultView; } diff --git a/src/com/android/contacts/common/vcard/ExportProcessor.java b/src/com/android/contacts/common/vcard/ExportProcessor.java index d7b2c0fc..ecf4f8a2 100644 --- a/src/com/android/contacts/common/vcard/ExportProcessor.java +++ b/src/com/android/contacts/common/vcard/ExportProcessor.java @@ -56,6 +56,7 @@ public class ExportProcessor extends ProcessorBase { private volatile boolean mCanceled; private volatile boolean mDone; + private String selExport = ""; public ExportProcessor(VCardService service, ExportRequest exportRequest, int jobId, String callingActivity) { @@ -141,7 +142,7 @@ public class ExportProcessor extends ProcessorBase { final Uri contentUriForRawContactsEntity = RawContactsEntity.CONTENT_URI; // TODO: should provide better selection. if (!composer.init(Contacts.CONTENT_URI, new String[] {Contacts._ID}, - null, null, + selExport, null, null, contentUriForRawContactsEntity)) { final String errorReason = composer.getErrorReason(); Log.e(LOG_TAG, "initialization of vCard composer failed: " + errorReason); @@ -217,6 +218,10 @@ public class ExportProcessor extends ProcessorBase { } } + public void setSelExport(String sel) { + selExport = sel; + } + private String translateComposerError(String errorMessage) { final Resources resources = mService.getResources(); if (VCardComposer.FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO.equals(errorMessage)) { diff --git a/src/com/android/contacts/common/vcard/ExportVCardActivity.java b/src/com/android/contacts/common/vcard/ExportVCardActivity.java index 06448ebc..94be8ee1 100644 --- a/src/com/android/contacts/common/vcard/ExportVCardActivity.java +++ b/src/com/android/contacts/common/vcard/ExportVCardActivity.java @@ -46,6 +46,7 @@ public class ExportVCardActivity extends Activity implements ServiceConnection, DialogInterface.OnClickListener, DialogInterface.OnCancelListener { private static final String LOG_TAG = "VCardExport"; private static final boolean DEBUG = VCardService.DEBUG; + private String selExport = ""; private static final int REQUEST_CREATE_DOCUMENT = 100; /** @@ -98,6 +99,10 @@ public class ExportVCardActivity extends Activity implements ServiceConnection, showDialog(R.id.dialog_fail_to_export_with_reason); } // Continued to onServiceConnected() + Intent selExportIntent = getIntent(); + if(selExportIntent != null) { + selExport = selExportIntent.getStringExtra("SelExport"); + } } @Override diff --git a/src/com/android/contacts/common/vcard/ImportVCardActivity.java b/src/com/android/contacts/common/vcard/ImportVCardActivity.java index 85b1417e..642c3da1 100644 --- a/src/com/android/contacts/common/vcard/ImportVCardActivity.java +++ b/src/com/android/contacts/common/vcard/ImportVCardActivity.java @@ -36,6 +36,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; import android.provider.OpenableColumns; +import android.provider.Telephony.Mms.Part; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; @@ -194,6 +195,17 @@ public class ImportVCardActivity extends Activity { } } + private String getProjection(Uri sourceUri) { + String projection = null; + if (sourceUri.getAuthority().startsWith("mms")) { + // to deal with the vcard received from mms. + projection = Part.NAME; + } else { + projection = OpenableColumns.DISPLAY_NAME; + } + return projection; + } + @Override public void run() { Log.i(LOG_TAG, "vCard cache thread starts running."); @@ -270,14 +282,14 @@ public class ImportVCardActivity extends Activity { // pick up the last part of the Uri. try { cursor = resolver.query(sourceUri, - new String[] { OpenableColumns.DISPLAY_NAME }, + new String[] { getProjection(sourceUri) }, null, null, null); if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) { if (cursor.getCount() > 1) { Log.w(LOG_TAG, "Unexpected multiple rows: " + cursor.getCount()); } - int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + int index = cursor.getColumnIndex(getProjection(sourceUri)); if (index >= 0) { displayName = cursor.getString(index); } diff --git a/src/com/android/contacts/common/vcard/SelectAccountActivity.java b/src/com/android/contacts/common/vcard/SelectAccountActivity.java index 66bfbca1..d8596378 100644 --- a/src/com/android/contacts/common/vcard/SelectAccountActivity.java +++ b/src/com/android/contacts/common/vcard/SelectAccountActivity.java @@ -58,7 +58,8 @@ public class SelectAccountActivity extends Activity { // - 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.getAccounts(true); + final List<AccountWithDataSet> accountList = accountTypes.getAccounts(true, + AccountTypeManager.FLAG_ALL_ACCOUNTS_WITHOUT_SIM); if (accountList.size() == 0) { Log.w(LOG_TAG, "Account does not exist"); finish(); @@ -106,7 +107,7 @@ public class SelectAccountActivity extends Activity { } return AccountSelectionUtil.getSelectAccountDialog(this, resId, mAccountSelectionListener, - new CancelListener()); + new CancelListener(), false); } } return super.onCreateDialog(resId, bundle); diff --git a/src/com/android/contacts/common/vcard/VCardService.java b/src/com/android/contacts/common/vcard/VCardService.java index 1d7837b1..cea79b71 100644 --- a/src/com/android/contacts/common/vcard/VCardService.java +++ b/src/com/android/contacts/common/vcard/VCardService.java @@ -67,6 +67,7 @@ public class VCardService extends Service { /* package */ static final String CACHE_FILE_PREFIX = "import_tmp_"; /* package */ static final String X_VCARD_MIME_TYPE = "text/x-vcard"; + private String selExport = ""; private class CustomMediaScannerConnectionClient implements MediaScannerConnectionClient { final MediaScannerConnection mConnection; @@ -189,7 +190,10 @@ public class VCardService extends Service { public synchronized void handleExportRequest(ExportRequest request, VCardImportExportListener listener) { - if (tryExecute(new ExportProcessor(this, request, mCurrentJobId, mCallingActivity))) { + ExportProcessor processor = new ExportProcessor(this, request, mCurrentJobId, + mCallingActivity); + processor.setSelExport(selExport); + if (tryExecute(processor)) { final String path = request.destUri.getEncodedPath(); if (DEBUG) Log.d(LOG_TAG, "Reserve the path " + path); if (!mReservedDestination.add(path)) { @@ -213,6 +217,10 @@ public class VCardService extends Service { } } + public void setSelExport(String sel) { + selExport = sel; + } + /** * Tries to call {@link ExecutorService#execute(Runnable)} toward a given processor. * @return true when successful. |