├── .classpath ├── .gitignore ├── .project ├── .settings └── org.eclipse.jdt.core.prefs ├── AndroidManifest.xml ├── README.md ├── proguard-project.txt ├── project.properties ├── release └── QuicKid.apk ├── res ├── anim │ ├── slide_in.xml │ └── slide_out.xml ├── drawable-hdpi │ ├── ic_contact_picture_holo_light.png │ ├── ic_launcher.png │ ├── quickcontact_badge_small_pressed.9.png │ └── quickcontact_badge_small_unpressed.9.png ├── drawable-mdpi │ └── ic_contact_picture_holo_light.png ├── drawable-xhdpi │ ├── btn_call_pressed.png │ ├── dial_num_pound_wht.png │ ├── dial_num_star_wht.png │ ├── ic_add_contact_holo_dark.png │ ├── ic_add_person_dk.png │ ├── ic_call_incoming_holo_dark.png │ ├── ic_call_missed_holo_dark.png │ ├── ic_call_outgoing_holo_dark.png │ ├── ic_call_voicemail_holo_dark.png │ ├── ic_contact_picture_holo_light.png │ ├── ic_dial_action_call.png │ ├── ic_dial_action_delete.png │ ├── ic_dial_action_vm.png │ ├── ic_launcher.png │ ├── ic_list_item_avatar.png │ ├── ic_menu_contacts.png │ ├── ic_menu_dialpad_lt.png │ ├── ic_menu_history_lt.png │ ├── ic_menu_overflow_lt.png │ ├── ic_menu_send_message.png │ ├── list_focused_holo.9.png │ ├── list_pressed_holo_dark.9.png │ ├── list_selector_disabled_holo_dark.9.png │ └── sms.png ├── drawable-xxhdpi │ └── ic_launcher.png ├── drawable │ ├── bg_contact_item_normal.xml │ ├── btn_call.xml │ ├── dialpad_key_colors.xml │ ├── quickcontact_badge_small.xml │ └── shadow_fade_up.xml ├── layout │ ├── activity_all_contact.xml │ ├── activity_history_dial.xml │ ├── activity_main.xml │ ├── dialpad.xml │ ├── dialpad_digits.xml │ ├── dialpad_key.xml │ ├── fragment_dialer.xml │ ├── fragment_recent.xml │ ├── fragment_search.xml │ ├── layout_add_contact.xml │ ├── layout_calllog_item.xml │ ├── layout_contact_item.xml │ ├── layout_phone_view.xml │ └── layout_splitter_view.xml ├── values-v14 │ └── styles.xml └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── src └── net └── nashlegend └── quickid ├── AllContactActivity.java ├── AppApplication.java ├── HistoryDialActivity.java ├── MainActivity.java ├── adapter ├── AllContactsAdapter.java ├── CallLogsAdapter.java └── ContactAdapter.java ├── fragment ├── DialpadFragment.java ├── RecentContactsFragment.java └── SearchFragment.java ├── interfacc ├── OnContactInteractListener.java ├── OnListFragmentScrolledListener.java └── OnQueryContactListener.java ├── model ├── Contact.java └── RecentContact.java ├── service └── ContactService.java ├── util ├── Consts.java ├── ContactHelper.java ├── HanyuPinyinHelper.java ├── IconContainer.java ├── PinnedHeaderListView.java ├── SectionedBaseAdapter.java └── SpecialCharSequenceMgr.java └── view ├── CallLogView.java ├── ContactView.java ├── DialogNumberPanel.java ├── DialpadKeyButton.java ├── DigitsEditText.java ├── PhoneView.java ├── SideBar.java └── SplitterView.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | 3 | # Files for the Dalvik VM 4 | *.dex 5 | 6 | # Java class files 7 | *.class 8 | 9 | # Generated files 10 | bin/ 11 | gen/ 12 | 13 | # Gradle files 14 | .gradle/ 15 | build/ 16 | 17 | # Local configuration file (sdk path, etc) 18 | local.properties 19 | 20 | # Proguard folder generated by Eclipse 21 | proguard/ 22 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | QuicKid 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 3 | org.eclipse.jdt.core.compiler.compliance=1.6 4 | org.eclipse.jdt.core.compiler.source=1.6 5 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 46 | 47 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | QuicKid 2 | ======= 3 | 1. Quick-Dialog 4 | 2. Voice-Dialog 5 | 3. ~~Handwrite-Dialog~~ 6 | 7 | ## 先定义一下几个规则 ## 8 | 1. **完全匹配**。指输入字符串与联系人内某一匹配项完全匹配。基础匹配数值为600,无加减分项。 9 | 10 | >`PanZhiHui-->PanZhiHui` 11 | 12 | 2. **前置首字母完全匹配**。指输入字符串与联系人前几个首字母完全匹配。是前置首字母溢出匹配的特殊形式。~~有两个加分项,~~ 无加分项,减分项为不匹配的首字母个数。 13 | 14 | >`PZH-->PanZhiHui`。+2-0 15 | >`PZ-->PanZhiHui`。+2-1 16 | 17 | 3. **前置首字母溢出匹配**。指在匹配首字母的情况下,还匹配了某一个或者几个首字母后一段连贯的字符串。~~有一个加分项,~~加分项为匹配到的首字母个数,减分项为不匹配的首字母个数。 18 | 19 | >`PanZH-->PanZhiHui`。+1-0 20 | >`PZhiHui-->PanZhiHui`。+1-0 21 | >`PZHui-->PanZhiHui`。+1-0 22 | >`PZHu-->PanZhiHui`。+1-0 23 | >`PZhi-->PanZhiHui`。+1-1 24 | 25 | 4. **前置段匹配**。指一个长度为N的连贯字符与联系人内某一匹配项的前N个字符完全匹配。是前置首字母溢出匹配的特殊形式。~~*同时意味着前置首字母溢出匹配事实上不需要加分项,只要保证**前置首字母完全匹配**的加分项比它大就足够了*~~。 26 | 27 | >`panzh-->PanZhiHui` 28 | 29 | 5. **后置首字母完全匹配**。指输入字符串匹配除第一个首字母以外的其他几个连续首字母。~~有两个加分项,~~ 无加分项,减分项为不匹配的首字母个数。 30 | 31 | >`ZH-->PanZhiHui` 32 | 33 | 6. **后置首字母溢出匹配**。后置首字母完全匹配的情况下,还匹配了某一个或者几个首字母后一段连贯的字符串。~~有一个加分项,~~ 加分项为匹配的首字母的数量,减分项为不匹配的首字母个数。 34 | 35 | >`ZHu-->PanZhiHui`。+1-0 36 | >`Zh-->PanZhiRui`。+1-1 37 | 38 | 7. **后置段匹配**。指有一串长度为N的连贯字符与与联系人内某一匹配项的后半部的一段N个字符串匹配,且此连贯字符的开头位置必须是某一首字母位置。*是后置首字母溢出匹配的特殊形式,同时意味着后置首字母溢出匹配事实上不需要加分项,只要保证**后置首字母完全匹配**的加分项比它大就足够了**。 39 | 40 | >`ZhiHui/Zhi/Hui-->PanZhiHui` 41 | 42 | 8. **后置无头匹配**。指一串连贯字符在前7种全部未匹配成功的情况下,却被包含在字符串里。加分项为-index,减分项为长度差 43 | 44 | >`hiHui-->PanZhiHui` 45 | 46 | 查询时匹配以上8种,其他情况不匹配。 47 | 48 | ## 排列规则 ## 49 | 1. 查询出的列表将按匹配度排序,匹配度不是一个简单的数值,而是几个有优先级别的匹配字段。优先按照级别排序,级别相同,再按加分项排序,加分项相同,再按照减分项排序,减分项相同,再按照默认的sort_key排序,*加分项在大多数情况下是相同的*。优先级别从高到低如下。 50 | - 完全匹配 51 | - 前置首字母完全匹配、前置首字母溢出匹配、前置段匹配。(当只有一个字母时,按规则#1算) 52 | - 后置首字母完全匹配、后置首字母溢出匹配、后置段匹配。(当只有一个字母时,按规则#5算) 53 | - 后置无头匹配。(可以考虑摒弃此匹配,没有人会这么按,而按键出错的可能性导致无头匹配的可能性又极小,往往不是想要的结果) 54 | 2. 输入的一列查询字符串将同时与联系人的名字和电话匹配。 55 | 3. 对于一个联系人,他的名字可能有多种发音,这时候要取匹配度最高的。 56 | 4. 对于一个联系人,他可能有两个甚至更多的电话号码,匹配的时候要分别匹配,而不是单独取匹配度最高的。 57 | 58 | > 事实上仍然可以简单地使用一个数值来表示匹配度。只需要做到低一级的无论怎么加都不可能大于高一级,高一级的无论怎么减都不会小于低一级的即可,因此我们要这时我们就需要对加分和减分顶做一个限制。对于加分和减分做一些数量上的限制。比如说最多+32,最多-32。就目前的情况来看,最多只+2,而减分项有可能任意多。但是不可能太多,出于谨慎考虑,对于减分限制为99个,若减分99次仍然不能影响加分项,则一个加分项至少要是减分项的100倍,那么我们取减分项为0.01,加分项为1。而四个级别分别给出400,300,200,100的加分基础数值。这样仍旧可以获得一个+99的加分空间和-99的减分空间而不会越级。 59 | 60 | 匹配算法写起来比较麻烦的应该是前/后首字母溢出匹配算法 61 | 62 | ## 2014/8/6 星期三 21:48:56 update ## 63 | 64 | #### 优先级其实应该分成6级,分别对应#1,#2,#3,#5,#6,#8匹配规则。 #### 65 | 66 | 取减分项为0.01,加分项为1不变,对应六级基础数值分别是600,500,400,300,200,100。这样仍旧可以获得一个+99的加分空间和-99的减分空间而不会越级。 67 | 68 | **匹配的原则是匹配尽可能多的单词** 69 | 70 | 这六个匹配规则还可以分成两大类。 71 | 1. 前置匹配。 72 | 2. 后置匹配。 73 | 74 | ## 2014/8/7 星期四 18:04:25 update ## 75 | 76 | #2,#4均为#3的特殊形式; 77 | #1看起来是#3的特殊形式,但是它的匹配加分规则不同,所以不属于#3; 78 | #5,#7均为#6的特殊形式; 79 | #8不属于以上任何形式,事实上#8存在的意义不要不是用来匹配姓名,而是匹配一段电话号码; 80 | 81 | #### 因此再次分为4级,分别为#1,#3,#6,#8 ####; 82 | 取减分项为0.001,加分项为1,对应四级基础数值分别是4000,3000,2000,1000。这样可以获得一个+999的加分空间和-999的减分空间而不会越级,采取这么大的数值纯属无聊。 -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | android.library.reference.1=../LegendUtils 16 | -------------------------------------------------------------------------------- /release/QuicKid.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/release/QuicKid.apk -------------------------------------------------------------------------------- /res/anim/slide_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /res/anim/slide_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_contact_picture_holo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-hdpi/ic_contact_picture_holo_light.png -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-hdpi/quickcontact_badge_small_pressed.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png -------------------------------------------------------------------------------- /res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_contact_picture_holo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-mdpi/ic_contact_picture_holo_light.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/btn_call_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/btn_call_pressed.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/dial_num_pound_wht.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/dial_num_pound_wht.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/dial_num_star_wht.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/dial_num_star_wht.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_add_contact_holo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_add_contact_holo_dark.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_add_person_dk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_add_person_dk.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_call_incoming_holo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_call_incoming_holo_dark.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_call_missed_holo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_call_missed_holo_dark.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_call_outgoing_holo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_call_outgoing_holo_dark.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_call_voicemail_holo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_call_voicemail_holo_dark.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_contact_picture_holo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_contact_picture_holo_light.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_dial_action_call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_dial_action_call.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_dial_action_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_dial_action_delete.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_dial_action_vm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_dial_action_vm.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_list_item_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_list_item_avatar.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_menu_contacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_menu_contacts.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_menu_dialpad_lt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_menu_dialpad_lt.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_menu_history_lt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_menu_history_lt.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_menu_overflow_lt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_menu_overflow_lt.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_menu_send_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/ic_menu_send_message.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/list_focused_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/list_focused_holo.9.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/list_pressed_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/list_pressed_holo_dark.9.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/list_selector_disabled_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/list_selector_disabled_holo_dark.9.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/sms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xhdpi/sms.png -------------------------------------------------------------------------------- /res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NashLegend/QuicKid/9938b8889d27729b2f9a43809e82ea7113690e42/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable/bg_contact_item_normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /res/drawable/btn_call.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /res/drawable/dialpad_key_colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /res/drawable/quickcontact_badge_small.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 24 | 25 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /res/drawable/shadow_fade_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 19 | 24 | -------------------------------------------------------------------------------- /res/layout/activity_all_contact.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /res/layout/activity_history_dial.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 17 | 18 | 23 | 24 | 25 | 30 | 31 | 39 | 40 | 48 | 49 | 58 | 59 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /res/layout/dialpad.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 23 | 24 | 30 | 31 | 32 | 33 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 62 | 63 | 66 | 67 | 70 | 71 | 72 | 73 | 74 | 77 | 78 | 79 | 80 | 83 | 84 | 87 | 88 | 89 | 90 | 93 | 94 | 97 | 98 | 99 | 100 | 103 | 104 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /res/layout/dialpad_digits.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 29 | 30 | 40 | 41 | -------------------------------------------------------------------------------- /res/layout/dialpad_key.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 12 | 13 | 16 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /res/layout/fragment_dialer.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 22 | 23 | 33 | 34 | 38 | 39 | 42 | 43 | 44 | 45 | 48 | 49 | 50 | 51 | 54 | 55 | 59 | 60 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /res/layout/fragment_recent.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /res/layout/fragment_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /res/layout/layout_add_contact.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 28 | 29 | 36 | 37 | 38 | 42 | 43 | -------------------------------------------------------------------------------- /res/layout/layout_calllog_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 | 29 | 30 | 38 | 39 | 45 | 46 | 50 | 51 | 58 | 59 | 60 | 65 | 66 | 73 | 74 | 82 | 83 | 84 | 85 | 89 | 90 | 97 | 98 | 99 | 100 | 104 | 105 | -------------------------------------------------------------------------------- /res/layout/layout_contact_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 19 | 20 | 27 | 28 | 35 | 36 | 40 | 41 | 46 | 47 | 53 | 54 | 55 | 61 | 62 | 63 | 67 | 68 | 75 | 76 | 77 | 78 | 83 | 84 | 85 | 89 | 90 | -------------------------------------------------------------------------------- /res/layout/layout_phone_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 24 | 25 | 30 | 31 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /res/layout/layout_splitter_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 | 26 | 27 | -------------------------------------------------------------------------------- /res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #3B77E7 5 | #000000 6 | #ececec 7 | #ffffff 8 | #8b8b8b 9 | #dadada 10 | #999c9c9c 11 | #559c9c9c 12 | 13 | 14 | #33b679 15 | #536173 16 | #855e86 17 | #df5948 18 | #aeb857 19 | #547bca 20 | #ae6b23 21 | #e5ae4f 22 | 23 | 24 | #cccccc 25 | #ffffff 26 | 27 | -------------------------------------------------------------------------------- /res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 30dp 5 | 80dp 6 | 8dp 7 | 64dp 8 | 64dp 9 | 16sp 10 | 12dp 11 | 12dp 12 | 80dp 13 | 4dp 14 | 35sp 15 | 60dp 16 | 56dp 17 | 47dp 18 | 16dp 19 | 40sp 20 | 13sp 21 | 26sp 22 | 30dp 23 | 50dp 24 | 56dp 25 | 18sp 26 | 11dp 27 | 5dp 28 | 0px 29 | 3dp 30 | 2dp 31 | 32 | 20 33 | 65 34 | 15 35 | 36 | 2dp 37 | 38 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 快拨 5 | Hello world! 6 | 0 7 | 1 8 | 2 9 | 3 10 | 4 11 | 5 12 | 6 13 | 7 14 | 8 15 | 9 16 | * 17 | # 18 | + 19 | 20 | ABC 21 | DEF 22 | GHI 23 | JKL 24 | MNO 25 | PQRS 26 | TUV 27 | WXYZ 28 | 29 | 30 | "一" 31 | "二" 32 | "三" 33 | "四" 34 | "五" 35 | "六" 36 | "七" 37 | "八" 38 | "九" 39 | "星标" 40 | "零" 41 | "英镑" 42 | "Backspace 键" 43 | "拨号" 44 | "通话记录" 45 | dial pad 46 | "更多选项" 47 | 全部联系人 48 | 通话记录 49 | 50 | -------------------------------------------------------------------------------- /res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 20 | 21 | 24 | 25 | 32 | 40 | 48 | 53 | 61 | 69 | 77 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/AllContactActivity.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid; 3 | 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.Iterator; 7 | 8 | import net.nashlegend.quickid.R; 9 | 10 | import net.nashlegend.quickid.adapter.AllContactsAdapter; 11 | import net.nashlegend.quickid.model.Contact; 12 | import net.nashlegend.quickid.util.Consts; 13 | import net.nashlegend.quickid.util.ContactHelper; 14 | import net.nashlegend.quickid.util.PinnedHeaderListView; 15 | import net.nashlegend.quickid.view.SideBar; 16 | 17 | 18 | import android.app.Activity; 19 | import android.content.BroadcastReceiver; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.content.IntentFilter; 23 | import android.os.Bundle; 24 | import android.util.SparseArray; 25 | 26 | public class AllContactActivity extends Activity { 27 | private ContactUpdateReceiver receiver; 28 | private final SparseArray> contactMaps = new SparseArray>(); 29 | private final SparseArray charMaps = new SparseArray(); 30 | private final HashMap keyMaps = new HashMap(); 31 | PinnedHeaderListView listView; 32 | AllContactsAdapter adapter; 33 | SideBar sideBar; 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.activity_all_contact); 39 | 40 | listView = (PinnedHeaderListView) findViewById(R.id.listview_all_contacts); 41 | adapter = new AllContactsAdapter(this); 42 | listView.setAdapter(adapter); 43 | sideBar = (SideBar) findViewById(R.id.sidebar_all_contacts); 44 | sideBar.setOnTouchingLetterChangedListener(new OnTouchingLetterChangedListener()); 45 | 46 | IntentFilter filter = new IntentFilter(); 47 | filter.addAction(Consts.Action_All_Contacts_Changed); 48 | filter.addAction(Consts.Action_Delete_One_Contact_From_All); 49 | receiver = new ContactUpdateReceiver(); 50 | registerReceiver(receiver, filter); 51 | 52 | updateData(); 53 | } 54 | 55 | class OnTouchingLetterChangedListener implements SideBar.OnTouchingLetterChangedListener { 56 | 57 | @Override 58 | public void onTouchingLetterChanged(final String s) { 59 | if (keyMaps.get(s) != null) { 60 | int idx = keyMaps.get(s); 61 | listView.setSelection(idx); 62 | } 63 | } 64 | 65 | } 66 | 67 | private void updateData() { 68 | ContactHelper.splitAllContacts(contactMaps, charMaps); 69 | adapter.setData(contactMaps, charMaps); 70 | int pos = 0; 71 | String[] chars = new String[charMaps.size()]; 72 | for (int i = 0; i < charMaps.size(); i++) { 73 | int idx = charMaps.keyAt(i); 74 | keyMaps.put(charMaps.get(idx), pos); 75 | chars[i] = charMaps.get(i); 76 | pos += contactMaps.get(idx).size() + 1; 77 | } 78 | sideBar.setChars(chars); 79 | sideBar.invalidate(); 80 | adapter.notifyDataSetChanged(); 81 | } 82 | 83 | @Override 84 | protected void onDestroy() { 85 | unregisterReceiver(receiver); 86 | super.onDestroy(); 87 | } 88 | 89 | public void onContactDeleted(long id) { 90 | for (int i = 0; i < AppApplication.AllContacts.size(); i++) { 91 | Contact type = AppApplication.AllContacts.get(i); 92 | if (id == type.getContactId()) { 93 | AppApplication.AllContacts.remove(i); 94 | updateData(); 95 | break; 96 | } 97 | } 98 | } 99 | 100 | class ContactUpdateReceiver extends BroadcastReceiver { 101 | 102 | @Override 103 | public void onReceive(Context context, Intent intent) { 104 | String action = intent.getAction(); 105 | if (Consts.Action_All_Contacts_Changed.equals(action)) { 106 | updateData(); 107 | } else if (Consts.Action_Delete_One_Contact_From_All.equals(action)) { 108 | long id = intent.getLongExtra(Consts.Extra_Contact_ID, -1L); 109 | onContactDeleted(id); 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/AppApplication.java: -------------------------------------------------------------------------------- 1 | package net.nashlegend.quickid; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | 6 | import net.nashlegend.quickid.model.Contact; 7 | import net.nashlegend.quickid.model.RecentContact; 8 | import net.nashlegend.quickid.service.ContactService; 9 | import net.nashlegend.quickid.util.ContactHelper; 10 | import net.nashlegend.quickid.util.HanyuPinyinHelper; 11 | 12 | 13 | import android.app.Application; 14 | import android.content.ContentResolver; 15 | import android.content.Intent; 16 | 17 | public class AppApplication extends Application { 18 | public static ArrayList AllContacts = new ArrayList(); 19 | public static ArrayList RecentContacts = new ArrayList(); 20 | public static HanyuPinyinHelper hanyuPinyinHelper; 21 | public static Application globalApplication; 22 | public static ContentResolver contentResolver; 23 | public static HashMap keyBoardMaps; 24 | 25 | public AppApplication() { 26 | 27 | } 28 | 29 | @Override 30 | public void onCreate() { 31 | super.onCreate(); 32 | globalApplication = this; 33 | hanyuPinyinHelper = new HanyuPinyinHelper(this); 34 | 35 | keyBoardMaps = new HashMap(); 36 | keyBoardMaps.put('a', '2'); 37 | keyBoardMaps.put('b', '2'); 38 | keyBoardMaps.put('c', '2'); 39 | keyBoardMaps.put('d', '3'); 40 | keyBoardMaps.put('e', '3'); 41 | keyBoardMaps.put('f', '3'); 42 | keyBoardMaps.put('g', '4'); 43 | keyBoardMaps.put('h', '4'); 44 | keyBoardMaps.put('i', '4'); 45 | keyBoardMaps.put('j', '5'); 46 | keyBoardMaps.put('k', '5'); 47 | keyBoardMaps.put('l', '5'); 48 | keyBoardMaps.put('m', '6'); 49 | keyBoardMaps.put('n', '6'); 50 | keyBoardMaps.put('o', '6'); 51 | keyBoardMaps.put('p', '7'); 52 | keyBoardMaps.put('q', '7'); 53 | keyBoardMaps.put('r', '7'); 54 | keyBoardMaps.put('s', '7'); 55 | keyBoardMaps.put('t', '8'); 56 | keyBoardMaps.put('u', '8'); 57 | keyBoardMaps.put('v', '8'); 58 | keyBoardMaps.put('w', '9'); 59 | keyBoardMaps.put('x', '9'); 60 | keyBoardMaps.put('y', '9'); 61 | keyBoardMaps.put('z', '9'); 62 | 63 | keyBoardMaps.put('A', '2'); 64 | keyBoardMaps.put('B', '2'); 65 | keyBoardMaps.put('C', '2'); 66 | keyBoardMaps.put('D', '3'); 67 | keyBoardMaps.put('E', '3'); 68 | keyBoardMaps.put('F', '3'); 69 | keyBoardMaps.put('G', '4'); 70 | keyBoardMaps.put('H', '4'); 71 | keyBoardMaps.put('I', '4'); 72 | keyBoardMaps.put('J', '5'); 73 | keyBoardMaps.put('K', '5'); 74 | keyBoardMaps.put('L', '5'); 75 | keyBoardMaps.put('M', '6'); 76 | keyBoardMaps.put('N', '6'); 77 | keyBoardMaps.put('O', '6'); 78 | keyBoardMaps.put('P', '7'); 79 | keyBoardMaps.put('Q', '7'); 80 | keyBoardMaps.put('R', '7'); 81 | keyBoardMaps.put('S', '7'); 82 | keyBoardMaps.put('T', '8'); 83 | keyBoardMaps.put('U', '8'); 84 | keyBoardMaps.put('V', '8'); 85 | keyBoardMaps.put('W', '9'); 86 | keyBoardMaps.put('X', '9'); 87 | keyBoardMaps.put('Y', '9'); 88 | keyBoardMaps.put('Z', '9'); 89 | 90 | Intent intent = new Intent(this, ContactService.class); 91 | startService(intent); 92 | ContactHelper.loadContacts(); 93 | ContactHelper.loadCallLogsCombined(); 94 | } 95 | 96 | public static ContentResolver getApplicationContentResolver() { 97 | if (contentResolver == null) { 98 | contentResolver = AppApplication.globalApplication 99 | .getContentResolver(); 100 | } 101 | return contentResolver; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/HistoryDialActivity.java: -------------------------------------------------------------------------------- 1 | package net.nashlegend.quickid; 2 | 3 | import net.nashlegend.quickid.R; 4 | 5 | import android.app.Activity; 6 | import android.os.Bundle; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | 10 | public class HistoryDialActivity extends Activity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_history_dial); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/MainActivity.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid; 3 | 4 | import net.nashlegend.quickid.R; 5 | 6 | import net.nashlegend.quickid.fragment.DialpadFragment; 7 | import net.nashlegend.quickid.fragment.RecentContactsFragment; 8 | import net.nashlegend.quickid.fragment.SearchFragment; 9 | import net.nashlegend.quickid.fragment.DialpadFragment.OnDialpadQueryChangedListener; 10 | import net.nashlegend.quickid.interfacc.OnListFragmentScrolledListener; 11 | import net.nashlegend.quickid.util.Consts; 12 | import net.nashlegend.quickid.util.ContactHelper; 13 | 14 | 15 | import android.app.Activity; 16 | import android.app.Fragment; 17 | import android.app.FragmentManager; 18 | import android.app.FragmentTransaction; 19 | import android.content.BroadcastReceiver; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.content.IntentFilter; 23 | import android.os.Bundle; 24 | import android.text.TextUtils; 25 | import android.view.View; 26 | import android.view.View.OnClickListener; 27 | import android.widget.ImageButton; 28 | import android.widget.AbsListView.OnScrollListener; 29 | 30 | public class MainActivity extends Activity implements OnClickListener, 31 | OnListFragmentScrolledListener, OnDialpadQueryChangedListener { 32 | 33 | private DialpadFragment mDialpadFragment; 34 | private RecentContactsFragment mFrequentDialFragment; 35 | private SearchFragment mSearchFragment; 36 | private ImageButton mDialpadButton; 37 | private ImageButton mDialButton; 38 | private ImageButton mSmsButton; 39 | private ImageButton mAllContactsButton; 40 | private static final String TAG_DIALPAD_FRAGMENT = "dialpad"; 41 | private static final String TAG_RECENT_DIAL_FRAGMENT = "recent"; 42 | private static final String TAG_SEARCH_FRAGMENT = "search"; 43 | private int currentStatus = 0; 44 | private ContactBroadcastReceiver contactBroadcastReceiver; 45 | private static int Status_Show_Frequent = 0; 46 | private static int Status_Show_Search = 1; 47 | 48 | @Override 49 | protected void onCreate(Bundle savedInstanceState) { 50 | super.onCreate(savedInstanceState); 51 | setContentView(R.layout.activity_main); 52 | if (savedInstanceState == null) { 53 | getFragmentManager() 54 | .beginTransaction() 55 | .add(R.id.layout_dial, new RecentContactsFragment(), 56 | TAG_RECENT_DIAL_FRAGMENT) 57 | .add(R.id.layout_dialer_panel, new DialpadFragment(), 58 | TAG_DIALPAD_FRAGMENT).commit(); 59 | } 60 | mAllContactsButton = (ImageButton) findViewById(R.id.button_all_contacts); 61 | mDialpadButton = (ImageButton) findViewById(R.id.button_dialpad); 62 | mDialButton = (ImageButton) findViewById(R.id.button_dial); 63 | mSmsButton = (ImageButton) findViewById(R.id.button_send_message); 64 | mDialButton.setOnClickListener(this); 65 | mSmsButton.setOnClickListener(this); 66 | mDialpadButton.setOnClickListener(this); 67 | mAllContactsButton.setOnClickListener(this); 68 | 69 | IntentFilter filter = new IntentFilter(); 70 | filter.addAction(Consts.Action_Delete_One_Contact_From_Search); 71 | filter.addAction(Consts.Action_Delete_One_Call_Log); 72 | filter.addAction(Consts.Action_Clear_Call_Log); 73 | contactBroadcastReceiver = new ContactBroadcastReceiver(); 74 | registerReceiver(contactBroadcastReceiver, filter); 75 | } 76 | 77 | @Override 78 | protected void onDestroy() { 79 | unregisterReceiver(contactBroadcastReceiver); 80 | super.onDestroy(); 81 | } 82 | 83 | class ContactBroadcastReceiver extends BroadcastReceiver { 84 | 85 | @Override 86 | public void onReceive(Context context, Intent intent) { 87 | String action = intent.getAction(); 88 | if (Consts.Action_Delete_One_Contact_From_Search.equals(action)) { 89 | long id = intent.getLongExtra(Consts.Extra_Contact_ID, -1L); 90 | mSearchFragment.onContactDeleted(id); 91 | return; 92 | } else if (Consts.Action_Delete_One_Call_Log.equals(action)) { 93 | String num = intent.getStringExtra(Consts.Extra_Calllog_Number); 94 | mFrequentDialFragment.onCallLogDeleted(num); 95 | return; 96 | } else if (Consts.Action_Clear_Call_Log.equals(action)) { 97 | mFrequentDialFragment.onCallLogCleared(); 98 | return; 99 | } 100 | } 101 | } 102 | 103 | @Override 104 | public void onAttachFragment(Fragment fragment) { 105 | if (fragment instanceof DialpadFragment) { 106 | mDialpadFragment = (DialpadFragment) fragment; 107 | final FragmentTransaction transaction = getFragmentManager() 108 | .beginTransaction(); 109 | transaction.hide(mDialpadFragment); 110 | transaction.commit(); 111 | } else if (fragment instanceof RecentContactsFragment) { 112 | mFrequentDialFragment = (RecentContactsFragment) fragment; 113 | } 114 | super.onAttachFragment(fragment); 115 | } 116 | 117 | private boolean isDialpadShowing() { 118 | return mDialpadFragment != null && mDialpadFragment.isVisible(); 119 | } 120 | 121 | private void enterSearchUi() { 122 | final FragmentTransaction transaction = getFragmentManager() 123 | .beginTransaction(); 124 | 125 | if (currentStatus == Status_Show_Frequent) { 126 | transaction.remove(mFrequentDialFragment); 127 | } 128 | 129 | final String tag = TAG_SEARCH_FRAGMENT; 130 | 131 | mSearchFragment = (SearchFragment) getFragmentManager() 132 | .findFragmentByTag(tag); 133 | if (mSearchFragment == null) { 134 | mSearchFragment = new SearchFragment(); 135 | } 136 | transaction.replace(R.id.layout_dial, mSearchFragment, tag); 137 | transaction.addToBackStack(null); 138 | transaction.commit(); 139 | currentStatus = Status_Show_Search; 140 | } 141 | 142 | public void exitSearchUi() { 143 | if (currentStatus == Status_Show_Search) { 144 | getFragmentManager().popBackStack(0, 145 | FragmentManager.POP_BACK_STACK_INCLUSIVE); 146 | currentStatus = Status_Show_Frequent; 147 | } 148 | } 149 | 150 | public void showDialpad(boolean animate) { 151 | // TODO 有一个bug,就是在hideDialpad动画没有完成之前就执行showDialpad的话,会导致永久hide 152 | // 这个bug在系统的拨号App中同样存在 153 | mDialpadFragment.setAdjustTranslationForAnimation(animate); 154 | mDialpadButton.setVisibility(View.GONE); 155 | mDialButton.setVisibility(View.VISIBLE); 156 | mSmsButton.setVisibility(View.VISIBLE); 157 | final FragmentTransaction ft = getFragmentManager().beginTransaction(); 158 | if (animate) { 159 | ft.setCustomAnimations(R.anim.slide_in, 0); 160 | } else { 161 | mDialpadFragment.setYFraction(0); 162 | } 163 | ft.show(mDialpadFragment); 164 | ft.commit(); 165 | } 166 | 167 | public void hideDialpad(boolean animate, boolean clearDialpad) { 168 | if (mDialpadFragment == null) 169 | return; 170 | mDialButton.setVisibility(View.GONE); 171 | mSmsButton.setVisibility(View.GONE); 172 | mDialpadButton.setVisibility(View.VISIBLE); 173 | if (clearDialpad) { 174 | mDialpadFragment.clearDialpad(); 175 | } 176 | if (!mDialpadFragment.isVisible()) 177 | return; 178 | mDialpadFragment.setAdjustTranslationForAnimation(animate); 179 | final FragmentTransaction ft = getFragmentManager().beginTransaction(); 180 | if (animate) { 181 | ft.setCustomAnimations(0, R.anim.slide_out); 182 | } 183 | ft.hide(mDialpadFragment); 184 | ft.commit(); 185 | } 186 | 187 | @Override 188 | public void onBackPressed() { 189 | if (isDialpadShowing()) { 190 | hideDialpad(true, false); 191 | return; 192 | } 193 | if (currentStatus == Status_Show_Search) { 194 | mDialpadFragment.clearDialpad(); 195 | currentStatus = Status_Show_Frequent; 196 | return; 197 | } 198 | 199 | moveTaskToBack(false); 200 | } 201 | 202 | @Override 203 | public void onClick(View v) { 204 | switch (v.getId()) { 205 | case R.id.button_dialpad: 206 | showDialpad(true); 207 | break; 208 | case R.id.button_dial: 209 | if (isDialpadShowing()) { 210 | String number = mDialpadFragment.getDiapadNumber(); 211 | if (!TextUtils.isEmpty(number) && number.length() >= 3) { 212 | ContactHelper.makePhoneCall(number); 213 | } 214 | } 215 | break; 216 | case R.id.button_send_message: 217 | if (isDialpadShowing()) { 218 | String number = mDialpadFragment.getDiapadNumber(); 219 | if (!TextUtils.isEmpty(number) && number.length() >= 3) { 220 | ContactHelper.sendSMS(number); 221 | } 222 | } 223 | break; 224 | case R.id.button_all_contacts: 225 | Intent intent = new Intent(this, AllContactActivity.class); 226 | startActivity(intent); 227 | break; 228 | default: 229 | break; 230 | } 231 | } 232 | 233 | @Override 234 | public void onDialpadQueryChanged(String query) { 235 | if (TextUtils.isEmpty(query)) { 236 | exitSearchUi(); 237 | } else { 238 | if (currentStatus == Status_Show_Frequent) { 239 | enterSearchUi(); 240 | } 241 | mSearchFragment.onQueryChanged(query); 242 | } 243 | } 244 | 245 | @Override 246 | public void onListFragmentScrollStateChange(int scrollState) { 247 | if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 248 | hideDialpad(true, false); 249 | } 250 | } 251 | 252 | } 253 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/adapter/AllContactsAdapter.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.adapter; 3 | 4 | import java.util.ArrayList; 5 | 6 | import net.nashlegend.quickid.model.Contact; 7 | import net.nashlegend.quickid.util.SectionedBaseAdapter; 8 | import net.nashlegend.quickid.view.ContactView; 9 | import net.nashlegend.quickid.view.SplitterView; 10 | 11 | import android.R.string; 12 | import android.content.Context; 13 | import android.util.SparseArray; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | 17 | 18 | public class AllContactsAdapter extends SectionedBaseAdapter { 19 | public SparseArray> contactMaps = new SparseArray>(); 20 | public SparseArray charMaps = new SparseArray(); 21 | private Context mContext; 22 | 23 | public AllContactsAdapter(Context context) { 24 | mContext = context; 25 | } 26 | 27 | public void setData(SparseArray> contactMaps, 28 | SparseArray charMaps) { 29 | this.contactMaps = contactMaps; 30 | this.charMaps = charMaps; 31 | } 32 | 33 | @Override 34 | public Object getItem(int section, int position) { 35 | return contactMaps.get(section).get(position); 36 | } 37 | 38 | @Override 39 | public long getItemId(int section, int position) { 40 | return 0; 41 | } 42 | 43 | @Override 44 | public int getSectionCount() { 45 | return charMaps.size(); 46 | } 47 | 48 | @Override 49 | public int getCountForSection(int section) { 50 | return contactMaps.get(section).size(); 51 | } 52 | 53 | @Override 54 | public View getItemView(int section, int position, View convertView, ViewGroup parent) { 55 | ViewHolder holder = null; 56 | if (convertView == null) { 57 | holder = new ViewHolder(); 58 | ContactView contactView = new ContactView(mContext, ContactView.Display_Mode_Display); 59 | holder.contactView = contactView; 60 | contactView.setTag(holder); 61 | } else { 62 | holder = (ViewHolder) convertView.getTag(); 63 | } 64 | holder.contactView.setContact((Contact) getItem(section, position)); 65 | holder.contactView.build(); 66 | return holder.contactView; 67 | } 68 | 69 | @Override 70 | public View getSectionHeaderView(int section, View convertView, ViewGroup parent) { 71 | ViewHolder holder = null; 72 | if (convertView == null) { 73 | holder = new ViewHolder(); 74 | SplitterView splitterView = new SplitterView(mContext); 75 | holder.splitterView = splitterView; 76 | splitterView.setTag(holder); 77 | } else { 78 | holder = (ViewHolder) convertView.getTag(); 79 | } 80 | holder.splitterView.build(charMaps.get(section)); 81 | return holder.splitterView; 82 | } 83 | 84 | class ViewHolder { 85 | ContactView contactView; 86 | SplitterView splitterView; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/adapter/CallLogsAdapter.java: -------------------------------------------------------------------------------- 1 | package net.nashlegend.quickid.adapter; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.Comparator; 6 | import java.util.Iterator; 7 | 8 | import net.nashlegend.quickid.AppApplication; 9 | import net.nashlegend.quickid.model.Contact; 10 | import net.nashlegend.quickid.model.Contact.PointPair; 11 | import net.nashlegend.quickid.model.Contact.ScoreAndHits; 12 | import net.nashlegend.quickid.view.CallLogView; 13 | import net.nashlegend.quickid.view.ContactView; 14 | 15 | 16 | import android.content.Context; 17 | import android.text.TextUtils; 18 | import android.view.View; 19 | import android.view.ViewGroup; 20 | import android.widget.BaseAdapter; 21 | import android.widget.Filter; 22 | import android.widget.Filterable; 23 | 24 | public class CallLogsAdapter extends BaseAdapter implements Filterable { 25 | 26 | private ArrayList contacts = new ArrayList(); 27 | private Context mContext; 28 | 29 | public CallLogsAdapter(Context context) { 30 | mContext = context; 31 | } 32 | 33 | @Override 34 | public int getCount() { 35 | return contacts.size(); 36 | } 37 | 38 | @Override 39 | public Object getItem(int position) { 40 | return contacts.get(position); 41 | } 42 | 43 | @Override 44 | public long getItemId(int position) { 45 | return position; 46 | } 47 | 48 | @Override 49 | public View getView(int position, View convertView, ViewGroup parent) { 50 | ViewHolder holder = null; 51 | if (convertView == null) { 52 | holder = new ViewHolder(); 53 | CallLogView callLogView = new CallLogView(mContext); 54 | holder.callLogView = callLogView; 55 | callLogView.setTag(holder); 56 | } else { 57 | holder = (ViewHolder) convertView.getTag(); 58 | } 59 | holder.callLogView.setContact(contacts.get(position)); 60 | holder.callLogView.build(); 61 | return holder.callLogView; 62 | } 63 | 64 | class ViewHolder { 65 | CallLogView callLogView; 66 | } 67 | 68 | public void sortContact(ArrayList lis) { 69 | ContactComparator comparator = new ContactComparator(); 70 | Collections.sort(lis, comparator); 71 | } 72 | 73 | public class ContactComparator implements Comparator { 74 | 75 | @Override 76 | public int compare(Contact lhs, Contact rhs) { 77 | if (lhs.matchValue.score > rhs.matchValue.score) { 78 | return -1; 79 | } else if (lhs.matchValue.score == rhs.matchValue.score) { 80 | return 0; 81 | } else { 82 | return 1; 83 | } 84 | } 85 | } 86 | 87 | public ArrayList getContacts() { 88 | return contacts; 89 | } 90 | 91 | public void setContacts(ArrayList contacts) { 92 | this.contacts = contacts; 93 | } 94 | 95 | @Override 96 | public Filter getFilter() { 97 | return filter; 98 | } 99 | 100 | private String preQueryString = ""; 101 | 102 | private Filter filter = new Filter() { 103 | @SuppressWarnings("unchecked") 104 | @Override 105 | protected void publishResults(CharSequence constraint, 106 | FilterResults results) { 107 | if (results != null) { 108 | setContacts((ArrayList) results.values); 109 | if (results.count > 0) { 110 | notifyDataSetChanged(); 111 | } else { 112 | notifyDataSetInvalidated(); 113 | } 114 | } 115 | } 116 | 117 | @Override 118 | protected FilterResults performFiltering(CharSequence constraint) { 119 | if (TextUtils.isEmpty(constraint) 120 | || preQueryString.equals(constraint)) { 121 | return null; 122 | } 123 | String queryString = constraint.toString(); 124 | FilterResults results = new FilterResults(); 125 | int preLength = preQueryString.length(); 126 | int queryLength = queryString.length(); 127 | ArrayList baseList = new ArrayList(); 128 | ArrayList resultList = new ArrayList(); 129 | if (preLength > 0 && (preLength == queryLength - 1) 130 | && queryString.startsWith(preQueryString)) { 131 | baseList = contacts; 132 | } else { 133 | baseList = AppApplication.AllContacts; 134 | } 135 | 136 | for (Iterator iterator = baseList.iterator(); iterator 137 | .hasNext();) { 138 | Contact contact = (Contact) iterator.next(); 139 | if (contact.match(queryString) > 0) { 140 | resultList.add(contact); 141 | } 142 | } 143 | sortContact(resultList); 144 | preQueryString = queryString; 145 | results.values = resultList; 146 | results.count = resultList.size(); 147 | return results; 148 | } 149 | }; 150 | } 151 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/adapter/ContactAdapter.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.adapter; 3 | 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.Comparator; 7 | import java.util.Iterator; 8 | 9 | import net.nashlegend.quickid.AppApplication; 10 | import net.nashlegend.quickid.model.Contact; 11 | import net.nashlegend.quickid.model.Contact.PointPair; 12 | import net.nashlegend.quickid.model.Contact.ScoreAndHits; 13 | import net.nashlegend.quickid.view.ContactView; 14 | 15 | import android.content.Context; 16 | import android.text.TextUtils; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.widget.BaseAdapter; 20 | import android.widget.Filter; 21 | import android.widget.Filterable; 22 | 23 | public class ContactAdapter extends BaseAdapter implements Filterable { 24 | 25 | private ArrayList contacts = new ArrayList(); 26 | private Context mContext; 27 | private int display_Mode = ContactView.Display_Mode_Display; 28 | 29 | public ContactAdapter(Context context, int display) { 30 | mContext = context; 31 | display_Mode = display; 32 | } 33 | 34 | public void clear() { 35 | preQueryString = ""; 36 | contacts.clear(); 37 | } 38 | 39 | @Override 40 | public int getCount() { 41 | return contacts.size(); 42 | } 43 | 44 | @Override 45 | public Object getItem(int position) { 46 | return contacts.get(position); 47 | } 48 | 49 | @Override 50 | public long getItemId(int position) { 51 | return position; 52 | } 53 | 54 | @Override 55 | public View getView(int position, View convertView, ViewGroup parent) { 56 | ViewHolder holder = null; 57 | if (convertView == null) { 58 | holder = new ViewHolder(); 59 | ContactView contactView = new ContactView(mContext, display_Mode); 60 | holder.contactView = contactView; 61 | contactView.setTag(holder); 62 | } else { 63 | holder = (ViewHolder) convertView.getTag(); 64 | } 65 | try { 66 | holder.contactView.setContact(contacts.get(position)); 67 | holder.contactView.build(); 68 | } catch (Exception e) { 69 | 70 | } 71 | 72 | return holder.contactView; 73 | } 74 | 75 | class ViewHolder { 76 | ContactView contactView; 77 | } 78 | 79 | public void sortContact(ArrayList lis) { 80 | ContactComparator comparator = new ContactComparator(); 81 | Collections.sort(lis, comparator); 82 | } 83 | 84 | public class ContactComparator implements Comparator { 85 | 86 | @Override 87 | public int compare(Contact lhs, Contact rhs) { 88 | if (lhs.matchValue.score > rhs.matchValue.score) { 89 | return -1; 90 | } else if (lhs.matchValue.score == rhs.matchValue.score) { 91 | if (lhs.matchValue.matchType == Contact.Match_Type_Name 92 | && lhs.matchValue.nameIndex < lhs.fullNamesString 93 | .size() 94 | && rhs.matchValue.nameIndex < rhs.fullNamesString 95 | .size()) { 96 | return lhs.fullNamesString.get(lhs.matchValue.nameIndex) 97 | .compareTo( 98 | rhs.fullNamesString 99 | .get(rhs.matchValue.nameIndex)); 100 | } 101 | return 0; 102 | } else { 103 | return 1; 104 | } 105 | } 106 | } 107 | 108 | public ArrayList getContacts() { 109 | return contacts; 110 | } 111 | 112 | public void setContacts(ArrayList contacts) { 113 | this.contacts = contacts; 114 | } 115 | 116 | @Override 117 | public Filter getFilter() { 118 | return filter; 119 | } 120 | 121 | private String preQueryString = ""; 122 | 123 | private Filter filter = new Filter() { 124 | @SuppressWarnings("unchecked") 125 | @Override 126 | protected void publishResults(CharSequence constraint, 127 | FilterResults results) { 128 | if (results != null) { 129 | setContacts((ArrayList) results.values); 130 | if (results.count > 0) { 131 | notifyDataSetChanged(); 132 | } else { 133 | notifyDataSetInvalidated(); 134 | } 135 | } 136 | } 137 | 138 | @Override 139 | synchronized protected FilterResults performFiltering(CharSequence constraint) { 140 | // 手速过快的话,有可能在执行这里的时候正在执行getView,这时候却修改了contact的内容,有可能报错 141 | // 所以这时候有三种方法解决这个问题。 142 | // 一是同步getView和performFiltering方法,让他们不相互打断,这很难实现,得重新实现adapter,listView 143 | // 二是执行performFiltering不修改contacts列表,这就要求使用contacts列表的一个clone,但是这样效率低下 144 | // 三是仍然允许performFiltering方法修改contacts内容,但是要在getView方法里做好预案 145 | // 当发现数据已经变得有问题的时候,直接返回不做处理,而当performFiltering执行完毕后再执行publishResults后。 146 | // 联系人列表将迅速发生改变,这样肉眼无法识别其实有那么20毫秒的时候里有几个联系人的匹配内容显示有问题。 147 | // 第三种方法要求performFiltering使用synchronized,并且setContacts(resultList)要写在此方法中 148 | 149 | // 2014-09-29 11:01:11 update 150 | // 上一次修改只处理了修改了单个contact的问题,但是还有另一个问题:setContacts();之后并没有立即notifyDataSetChanged(); 151 | // 在notifyDataSetChanged之后,adapter会顺序执行getView,但是在getView的时候,setContacts可能又会执行, 152 | // 从而改变了contacts的长度,contacts.get(position)可能会发生越界的问题,因此这时候getView要捕获这个错误 153 | // 返回一个空view,跟上次一样,空view存在时间很短,不会有人注意的…… 154 | if (TextUtils.isEmpty(constraint) 155 | || (preQueryString != null && preQueryString.equals(constraint))) { 156 | return null; 157 | } 158 | 159 | String queryString = constraint.toString(); 160 | FilterResults results = new FilterResults(); 161 | ArrayList baseList = new ArrayList(); 162 | ArrayList resultList = new ArrayList(); 163 | // 点击过快的话,第一个publishResults还没执行到,第二个performFiltering就已经开始了, 164 | // 如果使用contacts做baseList的话有可能导致搜索不到。 165 | // 就算是使用AllContacts做baseList基本没有问题,Nexus5 270联系人搜索不超过10ms 166 | 167 | baseList = AppApplication.AllContacts; 168 | for (Iterator iterator = baseList.iterator(); iterator 169 | .hasNext();) { 170 | Contact contact = (Contact) iterator.next(); 171 | if (contact.match(queryString) > 0) { 172 | resultList.add(contact); 173 | } 174 | } 175 | sortContact(resultList); 176 | preQueryString = queryString; 177 | results.values = resultList; 178 | results.count = resultList.size(); 179 | return results; 180 | } 181 | }; 182 | } 183 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/fragment/RecentContactsFragment.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.fragment; 3 | 4 | import net.nashlegend.quickid.AppApplication; 5 | import net.nashlegend.quickid.adapter.CallLogsAdapter; 6 | import net.nashlegend.quickid.model.Contact; 7 | import net.nashlegend.quickid.util.Consts; 8 | 9 | import net.nashlegend.quickid.R; 10 | 11 | import android.app.Activity; 12 | import android.app.Fragment; 13 | import android.content.BroadcastReceiver; 14 | import android.content.Context; 15 | import android.content.Intent; 16 | import android.content.IntentFilter; 17 | import android.os.Bundle; 18 | import android.text.TextUtils; 19 | import android.view.LayoutInflater; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | import android.widget.ListView; 23 | 24 | public class RecentContactsFragment extends Fragment { 25 | private CallLogsAdapter adapter; 26 | private ListView listView; 27 | 28 | public RecentContactsFragment() { 29 | // TODO Auto-generated constructor stub 30 | } 31 | 32 | @Override 33 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 34 | Bundle savedInstanceState) { 35 | View layoutView = inflater.inflate(R.layout.fragment_recent, container, 36 | false); 37 | listView = (ListView) layoutView 38 | .findViewById(R.id.listview_frequent_contact); 39 | adapter = new CallLogsAdapter(getActivity()); 40 | adapter.setContacts(AppApplication.RecentContacts); 41 | listView.setAdapter(adapter); 42 | registeReceiver(); 43 | loadCallLogs(); 44 | return layoutView; 45 | } 46 | 47 | public void onCallLogDeleted(String number) { 48 | if (TextUtils.isEmpty(number)) { 49 | return; 50 | } 51 | for (int i = 0; i < AppApplication.RecentContacts.size(); i++) { 52 | Contact type = AppApplication.RecentContacts.get(i); 53 | if (number.equals(type.Last_Contact_Number)) { 54 | AppApplication.RecentContacts.remove(i); 55 | adapter.notifyDataSetChanged(); 56 | break; 57 | } 58 | } 59 | } 60 | 61 | public void onCallLogCleared() { 62 | AppApplication.RecentContacts.clear(); 63 | adapter.notifyDataSetInvalidated(); 64 | } 65 | 66 | private void loadCallLogs() { 67 | adapter.setContacts(AppApplication.RecentContacts); 68 | adapter.notifyDataSetChanged(); 69 | } 70 | 71 | private void registeReceiver() { 72 | IntentFilter filter = new IntentFilter(); 73 | filter.addAction(Consts.Action_CallLogs_Changed); 74 | getActivity().registerReceiver(receiver, filter); 75 | } 76 | 77 | @Override 78 | public void onActivityCreated(Bundle savedInstanceState) { 79 | super.onActivityCreated(savedInstanceState); 80 | } 81 | 82 | @Override 83 | public void onAttach(Activity activity) { 84 | super.onAttach(activity); 85 | } 86 | 87 | @Override 88 | public void onResume() { 89 | super.onResume(); 90 | if (adapter != null) { 91 | if (needRetrive) { 92 | needRetrive = false; 93 | loadCallLogs(); 94 | }else { 95 | adapter.notifyDataSetChanged(); 96 | } 97 | } 98 | 99 | } 100 | 101 | @Override 102 | public void onDestroy() { 103 | try { 104 | getActivity().unregisterReceiver(receiver); 105 | } catch (Exception e) { 106 | 107 | } 108 | super.onDestroy(); 109 | } 110 | 111 | private boolean needRetrive = false; 112 | private DataChangeReceiver receiver = new DataChangeReceiver(); 113 | 114 | class DataChangeReceiver extends BroadcastReceiver { 115 | 116 | @Override 117 | public void onReceive(Context context, Intent intent) { 118 | if (Consts.Action_CallLogs_Changed.equals(intent.getAction())) { 119 | if (isVisible()) { 120 | loadCallLogs(); 121 | } else { 122 | needRetrive = true; 123 | } 124 | } 125 | } 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/fragment/SearchFragment.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.fragment; 3 | 4 | import net.nashlegend.quickid.adapter.ContactAdapter; 5 | import net.nashlegend.quickid.interfacc.OnListFragmentScrolledListener; 6 | import net.nashlegend.quickid.interfacc.OnQueryContactListener; 7 | import net.nashlegend.quickid.model.Contact; 8 | import net.nashlegend.quickid.util.Consts; 9 | import net.nashlegend.quickid.util.ContactHelper; 10 | import net.nashlegend.quickid.view.ContactView; 11 | import net.nashlegend.quickid.R; 12 | import android.app.Activity; 13 | import android.app.Fragment; 14 | import android.content.BroadcastReceiver; 15 | import android.content.Context; 16 | import android.content.Intent; 17 | import android.content.IntentFilter; 18 | import android.net.Uri; 19 | import android.os.Bundle; 20 | import android.os.Handler; 21 | import android.provider.ContactsContract; 22 | import android.text.TextUtils; 23 | import android.view.LayoutInflater; 24 | import android.view.View; 25 | import android.view.View.OnClickListener; 26 | import android.view.ViewGroup; 27 | import android.widget.AbsListView; 28 | import android.widget.AbsListView.OnScrollListener; 29 | import android.widget.ListView; 30 | 31 | public class SearchFragment extends Fragment implements OnQueryContactListener { 32 | 33 | private ContactAdapter adapter; 34 | private ListView listView; 35 | private OnListFragmentScrolledListener mActivityScrollListener; 36 | private View layoutView; 37 | private View footer;// 使用FooterView会导致快速点击的时候bm 38 | private String currentNumber; 39 | 40 | @Override 41 | public void onAttach(Activity activity) { 42 | super.onAttach(activity); 43 | try { 44 | mActivityScrollListener = (OnListFragmentScrolledListener) activity; 45 | } catch (ClassCastException e) { 46 | throw new ClassCastException(activity.toString() 47 | + " must implement OnListFragmentScrolledListener"); 48 | } 49 | } 50 | 51 | public SearchFragment() { 52 | 53 | } 54 | 55 | @Override 56 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 57 | Bundle savedInstanceState) { 58 | layoutView = inflater.inflate(R.layout.fragment_search, container, 59 | false); 60 | listView = (ListView) layoutView 61 | .findViewById(R.id.listview_search_contact); 62 | adapter = new ContactAdapter(getActivity(), ContactView.Display_Mode_Search); 63 | listView.setAdapter(adapter); 64 | listView.setOnScrollListener(new OnScrollListener() { 65 | 66 | @Override 67 | public void onScrollStateChanged(AbsListView view, int scrollState) { 68 | mActivityScrollListener 69 | .onListFragmentScrollStateChange(scrollState); 70 | } 71 | 72 | @Override 73 | public void onScroll(AbsListView view, int firstVisibleItem, 74 | int visibleItemCount, int totalItemCount) { 75 | 76 | } 77 | }); 78 | footer = inflater.inflate(R.layout.layout_add_contact, null); 79 | listView.addFooterView(footer); 80 | footer.setVisibility(View.GONE); 81 | footer.setOnClickListener(new OnClickListener() { 82 | 83 | @Override 84 | public void onClick(View v) { 85 | ContactHelper.addContact(currentNumber); 86 | } 87 | }); 88 | registeReceiver(); 89 | return layoutView; 90 | } 91 | 92 | public void onContactDeleted(long id) { 93 | for (int i = 0; i < adapter.getContacts().size(); i++) { 94 | if (adapter.getContacts().get(i).getContactId() == id) { 95 | adapter.getContacts().remove(i); 96 | adapter.notifyDataSetChanged(); 97 | break; 98 | } 99 | } 100 | } 101 | 102 | @Override 103 | public void onQueryChanged(final String queryString) { 104 | if (isAdded()) { 105 | currentNumber = queryString; 106 | if (TextUtils.isEmpty(queryString) || queryString.length() < 3) { 107 | footer.setVisibility(View.GONE); 108 | } else { 109 | footer.setVisibility(View.VISIBLE); 110 | } 111 | if (!TextUtils.isEmpty(queryString)) { 112 | adapter.getFilter().filter(queryString); 113 | } 114 | } else { 115 | new Handler().post(new Runnable() { 116 | @Override 117 | public void run() { 118 | onQueryChanged(queryString); 119 | } 120 | }); 121 | } 122 | } 123 | 124 | private void registeReceiver() { 125 | IntentFilter filter = new IntentFilter(); 126 | filter.addAction(Consts.Action_All_Contacts_Changed); 127 | getActivity().registerReceiver(receiver, filter); 128 | } 129 | 130 | @Override 131 | public void onResume() { 132 | super.onResume(); 133 | if (needRetrive) { 134 | needRetrive = false; 135 | updateData(); 136 | } 137 | } 138 | 139 | @Override 140 | public void onDestroy() { 141 | try { 142 | getActivity().unregisterReceiver(receiver); 143 | } catch (Exception e) { 144 | 145 | } 146 | super.onDestroy(); 147 | } 148 | 149 | boolean needRetrive = false; 150 | 151 | private void updateData() { 152 | if (!TextUtils.isEmpty(currentNumber)) { 153 | adapter.clear(); 154 | adapter.getFilter().filter(currentNumber); 155 | } 156 | } 157 | 158 | ContactUpdateReceiver receiver = new ContactUpdateReceiver(); 159 | 160 | class ContactUpdateReceiver extends BroadcastReceiver { 161 | 162 | @Override 163 | public void onReceive(Context context, Intent intent) { 164 | String action = intent.getAction(); 165 | if (Consts.Action_All_Contacts_Changed.equals(action)) { 166 | if (isVisible()) { 167 | updateData(); 168 | } else { 169 | needRetrive = true; 170 | } 171 | } 172 | } 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/interfacc/OnContactInteractListener.java: -------------------------------------------------------------------------------- 1 | package net.nashlegend.quickid.interfacc; 2 | 3 | public interface OnContactInteractListener { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/interfacc/OnListFragmentScrolledListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Google Inc. 3 | * Licensed to The Android Open Source Project. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package net.nashlegend.quickid.interfacc; 18 | 19 | /* 20 | * Interface to provide callback to activity when a child fragment is scrolled 21 | */ 22 | public interface OnListFragmentScrolledListener { 23 | public void onListFragmentScrollStateChange(int scrollState); 24 | } 25 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/interfacc/OnQueryContactListener.java: -------------------------------------------------------------------------------- 1 | package net.nashlegend.quickid.interfacc; 2 | 3 | public interface OnQueryContactListener { 4 | 5 | void onQueryChanged(String queryString); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/model/RecentContact.java: -------------------------------------------------------------------------------- 1 | package net.nashlegend.quickid.model; 2 | 3 | public class RecentContact { 4 | 5 | private long contractID; 6 | private int callType; 7 | private String name; 8 | private int numberType; 9 | private long date; 10 | private int duration; 11 | private String number; 12 | 13 | public RecentContact() { 14 | // TODO Auto-generated constructor stub 15 | } 16 | 17 | public long getContractID() { 18 | return contractID; 19 | } 20 | 21 | public void setContractID(long contractID) { 22 | this.contractID = contractID; 23 | } 24 | 25 | public int getCallType() { 26 | return callType; 27 | } 28 | 29 | public void setCallType(int callType) { 30 | this.callType = callType; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public void setName(String name) { 38 | this.name = name; 39 | } 40 | 41 | public int getNumberType() { 42 | return numberType; 43 | } 44 | 45 | public void setNumberType(int numberType) { 46 | this.numberType = numberType; 47 | } 48 | 49 | public long getDate() { 50 | return date; 51 | } 52 | 53 | public void setDate(long date) { 54 | this.date = date; 55 | } 56 | 57 | public int getDuration() { 58 | return duration; 59 | } 60 | 61 | public void setDuration(int duration) { 62 | this.duration = duration; 63 | } 64 | 65 | public String getNumber() { 66 | return number; 67 | } 68 | 69 | public void setNumber(String number) { 70 | this.number = number; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/service/ContactService.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.service; 3 | 4 | import java.util.Timer; 5 | 6 | import net.nashlegend.legendutils.Tools.TimerUtil; 7 | import net.nashlegend.quickid.AppApplication; 8 | import net.nashlegend.quickid.util.Consts; 9 | import net.nashlegend.quickid.util.ContactHelper; 10 | 11 | 12 | import android.app.Service; 13 | import android.content.Intent; 14 | import android.database.ContentObserver; 15 | import android.net.Uri; 16 | import android.os.Handler; 17 | import android.os.IBinder; 18 | import android.provider.CallLog.Calls; 19 | import android.provider.ContactsContract.Contacts; 20 | 21 | public class ContactService extends Service { 22 | private final Handler mHandler = new Handler(); 23 | public static final int gap = 500; 24 | 25 | public ContactService() { 26 | 27 | } 28 | 29 | @Override 30 | public void onCreate() { 31 | AppApplication.globalApplication.getContentResolver() 32 | .registerContentObserver(Contacts.CONTENT_URI, true, 33 | new ContactObserver()); 34 | AppApplication.globalApplication.getContentResolver() 35 | .registerContentObserver(Calls.CONTENT_URI, true, 36 | new CallLogsContactObserver()); 37 | super.onCreate(); 38 | } 39 | 40 | private final class ContactObserver extends ContentObserver { 41 | 42 | private boolean inwaitstate = false; 43 | private Timer updateTimer; 44 | 45 | public ContactObserver() { 46 | super(mHandler); 47 | } 48 | 49 | @Override 50 | public void onChange(boolean selfChange) { 51 | if (inwaitstate) { 52 | TimerUtil.clearTimeOut(updateTimer); 53 | } 54 | inwaitstate = true; 55 | updateTimer = TimerUtil.setTimeOut(runnable, gap); 56 | super.onChange(selfChange); 57 | } 58 | 59 | @Override 60 | public void onChange(boolean selfChange, Uri uri) { 61 | super.onChange(selfChange, uri); 62 | } 63 | 64 | private Runnable runnable = new Runnable() { 65 | 66 | @Override 67 | public void run() { 68 | ContactHelper.loadContacts(); 69 | Intent intent = new Intent(); 70 | intent.setAction(Consts.Action_All_Contacts_Changed); 71 | getApplicationContext().sendBroadcast(intent); 72 | } 73 | }; 74 | } 75 | 76 | private final class CallLogsContactObserver extends ContentObserver { 77 | 78 | private boolean inwaitstate = false; 79 | private Timer updateTimer; 80 | 81 | public CallLogsContactObserver() { 82 | super(mHandler); 83 | } 84 | 85 | @Override 86 | public void onChange(boolean selfChange) { 87 | if (inwaitstate) { 88 | TimerUtil.clearTimeOut(updateTimer); 89 | } 90 | inwaitstate = true; 91 | updateTimer = TimerUtil.setTimeOut(runnable, gap); 92 | super.onChange(selfChange); 93 | } 94 | 95 | @Override 96 | public void onChange(boolean selfChange, Uri uri) { 97 | super.onChange(selfChange, uri); 98 | } 99 | 100 | private Runnable runnable = new Runnable() { 101 | @Override 102 | public void run() { 103 | ContactHelper.loadCallLogsCombined(); 104 | Intent intent = new Intent(); 105 | intent.setAction(Consts.Action_CallLogs_Changed); 106 | getApplicationContext().sendBroadcast(intent); 107 | } 108 | }; 109 | } 110 | 111 | @Override 112 | public int onStartCommand(Intent intent, int flags, int startId) { 113 | return super.onStartCommand(intent, flags, startId); 114 | } 115 | 116 | @Override 117 | public IBinder onBind(Intent intent) { 118 | return null; 119 | } 120 | 121 | @Override 122 | public boolean onUnbind(Intent intent) { 123 | return super.onUnbind(intent); 124 | } 125 | 126 | @Override 127 | public void onDestroy() { 128 | super.onDestroy(); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/util/Consts.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.util; 3 | 4 | public class Consts { 5 | public static final String Action_All_Contacts_Changed = "quickid.action.allcontactschange"; 6 | public static final String Action_CallLogs_Changed = "quickid.action.calllogschange"; 7 | public static final String Action_Delete_One_Contact_From_Search = "quickid.action.deleteonecontactfromsearch"; 8 | public static final String Action_Delete_One_Contact_From_All = "quickid.action.deleteonecontactfromall"; 9 | public static final String Action_Delete_One_Call_Log = "quickid.action.deleteonecalllog"; 10 | public static final String Action_Clear_Call_Log = "quickid.action.clearcalllog"; 11 | 12 | public static final String Extra_Contact_ID = "quick.extra.contactid"; 13 | public static final String Extra_Calllog_Number = "quickid.extra.calllognumber"; 14 | public static final String Extra_Calllog_ID = "quickid.extra.calllogid"; 15 | } 16 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/util/ContactHelper.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.util; 3 | 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.Comparator; 7 | import java.util.HashMap; 8 | import java.util.Iterator; 9 | import java.util.LinkedHashMap; 10 | 11 | import net.nashlegend.quickid.AppApplication; 12 | import net.nashlegend.quickid.model.Contact; 13 | import net.nashlegend.quickid.model.Contact.PhoneStruct; 14 | 15 | import android.content.ContentResolver; 16 | import android.content.ContentUris; 17 | import android.content.Context; 18 | import android.content.Intent; 19 | import android.database.Cursor; 20 | import android.net.Uri; 21 | import android.os.Vibrator; 22 | import android.provider.CallLog.Calls; 23 | import android.provider.ContactsContract.CommonDataKinds.Phone; 24 | import android.provider.ContactsContract.Contacts; 25 | import android.provider.ContactsContract.PhoneLookup; 26 | import android.provider.ContactsContract.RawContacts; 27 | import android.text.TextUtils; 28 | import android.util.SparseArray; 29 | 30 | public class ContactHelper { 31 | 32 | /** 33 | * 加载所有联系人,没有手机号的未加载。TODO 34 | */ 35 | synchronized public static void loadContacts() { 36 | ArrayList AllContacts = new ArrayList(); 37 | ContentResolver resolver = AppApplication.globalApplication 38 | .getContentResolver(); 39 | // 要使用RawContacts.CONTACT_ID而不是Contacts.CONTACT_ID 40 | String[] PROJECTION = { 41 | RawContacts.CONTACT_ID, Contacts.DISPLAY_NAME, 42 | Contacts.LOOKUP_KEY, Contacts.PHOTO_THUMBNAIL_URI, 43 | Phone.NUMBER, Phone.TYPE, Contacts.STARRED 44 | }; 45 | Cursor cursor = resolver.query(Phone.CONTENT_URI, PROJECTION, null, 46 | null, Contacts.SORT_KEY_PRIMARY); 47 | String preLookupKey = ""; 48 | Contact preContact = null; 49 | if (cursor.moveToFirst()) { 50 | do { 51 | long contractID = cursor.getInt(0); 52 | String displayName = cursor.getString(1); 53 | String lookupKey = cursor.getString(2); 54 | String photoUri = cursor.getString(3); 55 | boolean starred = cursor.getInt(6) == 1; 56 | if (lookupKey.equals(preLookupKey) && preContact != null) { 57 | preContact.addPhone(cursor.getString(4), cursor.getInt(5)); 58 | } else { 59 | Contact contact = new Contact(); 60 | contact.setContactId(contractID); 61 | contact.setName(displayName); 62 | contact.setLookupKey(lookupKey); 63 | contact.setPhotoUri(photoUri); 64 | contact.addPhone(cursor.getString(4), cursor.getInt(5)); 65 | contact.setStarred(starred); 66 | AllContacts.add(contact); 67 | preLookupKey = lookupKey; 68 | preContact = contact; 69 | } 70 | } while (cursor.moveToNext()); 71 | } else { 72 | // No Phone Number Found 73 | } 74 | cursor.close(); 75 | AppApplication.AllContacts = AllContacts; 76 | } 77 | 78 | public static void splitAllContacts(SparseArray> contactMaps, 79 | SparseArray charMaps) { 80 | sortContact(AppApplication.AllContacts); 81 | contactMaps.clear(); 82 | charMaps.clear(); 83 | String currentIndexer = "ThisCannotBeIndexerHaHa~~"; 84 | Integer sectionIndex = -1; 85 | for (int i = 0; i < AppApplication.AllContacts.size(); i++) { 86 | Contact contact = AppApplication.AllContacts.get(i); 87 | if (currentIndexer.equals(contact.indexer)) { 88 | contactMaps.get(sectionIndex).add(contact); 89 | } else { 90 | sectionIndex++; 91 | currentIndexer = contact.indexer; 92 | charMaps.put(sectionIndex, currentIndexer); 93 | ArrayList lss = new ArrayList(); 94 | lss.add(contact); 95 | contactMaps.put(sectionIndex, lss); 96 | } 97 | } 98 | } 99 | 100 | public static void sortContact(ArrayList lis) { 101 | ContactComparator comparator = new ContactComparator(); 102 | Collections.sort(lis, comparator); 103 | } 104 | 105 | public static class ContactComparator implements Comparator { 106 | 107 | @Override 108 | public int compare(Contact lhs, Contact rhs) { 109 | return lhs.fullNamesString.get(0).compareToIgnoreCase(rhs.fullNamesString.get(0)); 110 | } 111 | } 112 | 113 | /** 114 | * 加载通话记录 115 | */ 116 | synchronized public static void loadCallLogsCombined() { 117 | if (AppApplication.AllContacts.size() == 0) { 118 | loadContacts(); 119 | } 120 | ArrayList recentContacts = new ArrayList(); 121 | String[] projection = { 122 | Calls._ID, Calls.TYPE, Calls.CACHED_NAME, 123 | Calls.CACHED_NUMBER_TYPE, Calls.DATE, Calls.DURATION, 124 | Calls.NUMBER 125 | }; 126 | ContentResolver resolver = AppApplication.globalApplication 127 | .getContentResolver(); 128 | Cursor cursor = resolver.query(Calls.CONTENT_URI, projection, null, 129 | null, Calls.DEFAULT_SORT_ORDER); 130 | while (cursor.moveToNext()) { 131 | long callID = cursor.getInt(0); 132 | int callType = cursor.getInt(1); 133 | String name = cursor.getString(2); 134 | int numberType = cursor.getInt(3); 135 | long date = cursor.getLong(4); 136 | int duration = cursor.getInt(5); 137 | String number = cursor.getString(6).replaceAll(" ", ""); 138 | // TODO 139 | boolean matched = false; 140 | for (Iterator iterator = recentContacts.iterator(); iterator 141 | .hasNext();) { 142 | Contact con = iterator.next(); 143 | if (con.Last_Contact_Number.equals(number)) { 144 | matched = true; 145 | con.Times_Contacted++; 146 | break; 147 | } 148 | } 149 | if (!matched) { 150 | match2: for (Iterator iterator = AppApplication.AllContacts 151 | .iterator(); iterator.hasNext();) { 152 | Contact con = iterator.next(); 153 | ArrayList phones = con.getPhones(); 154 | for (Iterator iterator2 = phones 155 | .iterator(); iterator2.hasNext();) { 156 | PhoneStruct phoneStruct = iterator2.next(); 157 | if (phoneStruct.phoneNumber.equals(number)) { 158 | matched = true; 159 | Contact tmpContact = con.clone(); 160 | tmpContact 161 | .setPhones(new ArrayList()); 162 | tmpContact.Times_Contacted = 1; 163 | tmpContact.Last_Contact_Call_ID = callID; 164 | tmpContact.Last_Contact_Call_Type = callType; 165 | tmpContact.Last_Contact_Number = number; 166 | tmpContact.Last_Contact_Phone_Type = numberType; 167 | tmpContact.Last_Time_Contacted = date; 168 | tmpContact.Last_Contact_Duration = duration; 169 | recentContacts.add(tmpContact); 170 | break match2; 171 | } 172 | } 173 | } 174 | } 175 | if (!matched) { 176 | Contact tmpContact = new Contact(); 177 | tmpContact.Times_Contacted = 1; 178 | tmpContact.Last_Contact_Call_ID = callID; 179 | tmpContact.Last_Contact_Call_Type = callType; 180 | tmpContact.Last_Contact_Number = number; 181 | tmpContact.Last_Contact_Phone_Type = numberType; 182 | tmpContact.Last_Time_Contacted = date; 183 | tmpContact.Last_Contact_Duration = duration; 184 | recentContacts.add(tmpContact); 185 | } 186 | } 187 | cursor.close(); 188 | AppApplication.RecentContacts = recentContacts; 189 | } 190 | 191 | public static int deleteContactsByID(long contact_id) { 192 | ContentResolver resolver = AppApplication.globalApplication 193 | .getContentResolver(); 194 | int res = resolver.delete(ContentUris.withAppendedId(Contacts.CONTENT_URI, contact_id), 195 | null, null); 196 | return res; 197 | } 198 | 199 | public static int deleteCallLogByCallID(long call_ID) { 200 | ContentResolver resolver = AppApplication.globalApplication 201 | .getContentResolver(); 202 | int res = resolver.delete(Calls.CONTENT_URI, Calls._ID + "=?", 203 | new String[] { 204 | String.valueOf(call_ID) 205 | }); 206 | return res; 207 | } 208 | 209 | public static int deleteCallLogByNumber(String number) { 210 | ContentResolver resolver = AppApplication.globalApplication 211 | .getContentResolver(); 212 | int res = resolver.delete(Calls.CONTENT_URI, Calls.NUMBER + " =?", 213 | new String[] { 214 | number 215 | }); 216 | return res; 217 | } 218 | 219 | public static void clearCallLogs() { 220 | ContentResolver resolver = AppApplication.globalApplication 221 | .getContentResolver(); 222 | if (resolver.delete(Calls.CONTENT_URI, null, null) > 0) { 223 | // delete ok 224 | } 225 | } 226 | 227 | /** 228 | * 加载最近联系人(和收藏联系人) 229 | */ 230 | public static void loadStrequent() { 231 | ArrayList StrequentContacts = new ArrayList(); 232 | String[] projection = { 233 | Contacts._ID, Contacts.DISPLAY_NAME, 234 | Contacts.LOOKUP_KEY, Contacts.PHOTO_THUMBNAIL_URI, 235 | Contacts.TIMES_CONTACTED, Contacts.LAST_TIME_CONTACTED, 236 | Contacts.STARRED, Contacts.PHOTO_ID 237 | }; 238 | ContentResolver resolver = AppApplication.globalApplication 239 | .getContentResolver(); 240 | // 显示最近联系人和收藏的联系人 241 | Cursor cursor = resolver.query(Contacts.CONTENT_STREQUENT_URI, 242 | projection, null, null, null); 243 | while (cursor.moveToNext()) { 244 | Contact contact = new Contact(); 245 | long contractID = cursor.getInt(0); 246 | String displayName = cursor.getString(1); 247 | String lookupKey = cursor.getString(2); 248 | String photoUri = cursor.getString(3); 249 | int TIMES_CONTACTED = cursor.getInt(4); 250 | long LAST_TIME_CONTACTED = cursor.getLong(5); 251 | boolean starred = cursor.getInt(6) == 1; 252 | contact.setContactId(contractID); 253 | contact.setName(displayName); 254 | contact.setLookupKey(lookupKey); 255 | contact.setPhotoUri(photoUri); 256 | contact.setStarred(starred); 257 | contact.Times_Contacted = TIMES_CONTACTED; 258 | contact.Last_Time_Contacted = LAST_TIME_CONTACTED; 259 | StrequentContacts.add(contact); 260 | } 261 | cursor.close(); 262 | // notify 263 | } 264 | 265 | public static void deleteStrequent(long contact_ID) { 266 | ContentResolver resolver = AppApplication.globalApplication 267 | .getContentResolver(); 268 | if (resolver.delete(Contacts.CONTENT_STREQUENT_URI, Contacts._ID + "=?", 269 | new String[] { 270 | String.valueOf(contact_ID) 271 | }) > 0) { 272 | // delete ok 273 | } 274 | } 275 | 276 | public static void clearStrequent() { 277 | ContentResolver resolver = AppApplication.globalApplication 278 | .getContentResolver(); 279 | if (resolver.delete(Contacts.CONTENT_STREQUENT_URI, null, null) > 0) { 280 | // delete ok 281 | } 282 | } 283 | 284 | /** 285 | * 根据电话号码寻出联系人 286 | * 287 | * @param contactNumber 288 | * @return 289 | */ 290 | public static Contact getContactByPhoneNumber(String contactNumber) { 291 | Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, 292 | Uri.encode(contactNumber)); 293 | return lookupContactFromUri(uri); 294 | } 295 | 296 | public static Contact lookupContactFromUri(Uri uri) { 297 | final Contact info; 298 | Cursor phonesCursor = AppApplication.globalApplication 299 | .getContentResolver().query(uri, PhoneQuery._PROJECTION, null, 300 | null, null); 301 | 302 | if (phonesCursor != null) { 303 | try { 304 | if (phonesCursor.moveToFirst()) { 305 | info = new Contact(); 306 | long contactId = phonesCursor.getLong(PhoneQuery.PERSON_ID); 307 | String lookupKey = phonesCursor 308 | .getString(PhoneQuery.LOOKUP_KEY); 309 | info.setLookupKey(lookupKey); 310 | info.setLookupUri(Contacts.getLookupUri(contactId, 311 | lookupKey)); 312 | info.setName(phonesCursor.getString(PhoneQuery.NAME)); 313 | 314 | info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE); 315 | info.label = phonesCursor.getString(PhoneQuery.LABEL); 316 | info.number = phonesCursor 317 | .getString(PhoneQuery.MATCHED_NUMBER); 318 | info.normalizedNumber = phonesCursor 319 | .getString(PhoneQuery.NORMALIZED_NUMBER); 320 | info.photoId = phonesCursor.getLong(PhoneQuery.PHOTO_ID); 321 | info.setPhotoUri(phonesCursor 322 | .getString(PhoneQuery.PHOTO_URI)); 323 | info.formattedNumber = null; 324 | } else { 325 | info = new Contact(); 326 | } 327 | } finally { 328 | phonesCursor.close(); 329 | } 330 | } else { 331 | info = null; 332 | } 333 | return info; 334 | } 335 | 336 | final static class PhoneQuery { 337 | public static final String[] _PROJECTION = new String[] { 338 | PhoneLookup._ID, PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE, 339 | PhoneLookup.LABEL, PhoneLookup.NUMBER, 340 | PhoneLookup.NORMALIZED_NUMBER, PhoneLookup.PHOTO_ID, 341 | PhoneLookup.LOOKUP_KEY, PhoneLookup.PHOTO_URI 342 | }; 343 | 344 | public static final int PERSON_ID = 0; 345 | public static final int NAME = 1; 346 | public static final int PHONE_TYPE = 2; 347 | public static final int LABEL = 3; 348 | public static final int MATCHED_NUMBER = 4; 349 | public static final int NORMALIZED_NUMBER = 5; 350 | public static final int PHOTO_ID = 6; 351 | public static final int LOOKUP_KEY = 7; 352 | public static final int PHOTO_URI = 8; 353 | } 354 | 355 | private static Vibrator vibrator; 356 | 357 | public static void vibrate(long duaration) { 358 | if (vibrator == null) { 359 | vibrator = (Vibrator) AppApplication.globalApplication 360 | .getSystemService(Context.VIBRATOR_SERVICE); 361 | } 362 | vibrator.vibrate(duaration); 363 | } 364 | 365 | public static void openContactDetail(long id) { 366 | Intent intent = new Intent(); 367 | intent.setAction(Intent.ACTION_VIEW); 368 | Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, id); 369 | intent.setData(contactUri); 370 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 371 | AppApplication.globalApplication.startActivity(intent); 372 | } 373 | 374 | public static void makePhoneCall(String number) { 375 | if (TextUtils.isEmpty(number) || number.length() < 3) { 376 | return; 377 | } 378 | Intent intent = new Intent(); 379 | intent.setAction(Intent.ACTION_CALL); 380 | intent.setData(Uri.parse("tel:" + number)); 381 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 382 | AppApplication.globalApplication.startActivity(intent); 383 | } 384 | 385 | public static void sendSMS(String number) { 386 | if (TextUtils.isEmpty(number) || number.length() < 3) { 387 | return; 388 | } 389 | Intent intent = new Intent(); 390 | intent.setAction(Intent.ACTION_SENDTO); 391 | intent.setData(Uri.parse("smsto:" + number)); 392 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 393 | AppApplication.globalApplication.startActivity(intent); 394 | } 395 | 396 | public static void addContact(String currentNumber) { 397 | if (currentNumber != null && currentNumber.length() > 2) { 398 | Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 399 | intent.setType("vnd.android.cursor.item/raw_contact"); 400 | intent.putExtra(android.provider.ContactsContract.Intents.Insert.PHONE, currentNumber); 401 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 402 | AppApplication.globalApplication.startActivity(intent); 403 | } 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/util/HanyuPinyinHelper.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.util; 3 | 4 | import android.annotation.SuppressLint; 5 | import android.content.Context; 6 | import android.text.TextUtils; 7 | import android.util.TypedValue; 8 | 9 | import java.io.BufferedInputStream; 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Properties; 14 | 15 | import net.nashlegend.legendutils.R; 16 | import net.nashlegend.legendutils.Tools.TextUtil; 17 | 18 | /** 19 | * 汉语转拼音Helper类 20 | */ 21 | @SuppressLint("DefaultLocale") 22 | public class HanyuPinyinHelper { 23 | 24 | private StringBuffer buffer = new StringBuffer(); 25 | private List list = new ArrayList(); 26 | private Properties allPinyin = new Properties(); 27 | /** 28 | * 是否仅仅输出首字母 29 | */ 30 | private boolean isSimple = false; 31 | 32 | public HanyuPinyinHelper(Context context) { 33 | init(context); 34 | } 35 | 36 | public void init(Context context) { 37 | try { 38 | TypedValue typedValue = new TypedValue(); 39 | allPinyin.load(context.getResources().openRawResource( 40 | R.raw.hanyu_pinyin, typedValue)); 41 | } catch (IOException e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | 46 | public String[] getHanyuPinyins(char c) { 47 | int codePointOfChar = c; 48 | String codepointHexStr = Integer.toHexString(codePointOfChar) 49 | .toUpperCase(); 50 | String str = (String) allPinyin.get(codepointHexStr); 51 | String[] strs = str.split(","); 52 | for (int i = 0; i < strs.length; i++) { 53 | strs[i] = UpperFirstLetter(strs[i]); 54 | } 55 | return strs; 56 | } 57 | 58 | public String UpperFirstLetter(String str) { 59 | if (str.length() > 1) { 60 | return String.valueOf(str.charAt(0)).toUpperCase() 61 | + str.substring(1); 62 | } else { 63 | return str.toUpperCase(); 64 | } 65 | } 66 | 67 | /** 68 | * @param str 要转换的字符 69 | * @param isSimple 只输出首字母(true)或者全部输出(false) 70 | * @return 拼音列表,带空格 71 | */ 72 | public List hanyuPinYinConvert(String str, boolean isSimple) { 73 | if (TextUtils.isEmpty(str)) 74 | return null; 75 | this.isSimple = isSimple; 76 | list = new ArrayList(); 77 | buffer.delete(0, buffer.length()); 78 | convert(0, str); 79 | return list; 80 | } 81 | 82 | /** 83 | * @param str 要转换的字符 84 | * @return 拼音列表 85 | */ 86 | public List hanyuPinYinConvert(String str) { 87 | if (str == null || "".equals(str)) 88 | return null; 89 | list = new ArrayList(); 90 | buffer.delete(0, buffer.length()); 91 | this.isSimple = true; 92 | convert(0, str); 93 | buffer.delete(0, buffer.length()); 94 | this.isSimple = false; 95 | convert(0, str); 96 | return list; 97 | } 98 | 99 | /** 100 | * @param n 101 | * @param str 要转换拼音的字符 102 | */ 103 | private void convert(int n, String str) { 104 | if (n == str.length()) { 105 | String temp = buffer.toString(); 106 | if (!list.contains(temp)) { 107 | list.add(buffer.toString().trim()); 108 | } 109 | return; 110 | } else { 111 | char c = str.charAt(n); 112 | // 0x3007是零,就是那个圈〇 113 | if (0x3007 == c || (0x4E00 <= c && c <= 0x9FA5)) { 114 | String[] arrayStrings = getHanyuPinyins(c); 115 | if (arrayStrings == null) { 116 | buffer.append(c); 117 | convert(n + 1, str); 118 | } else if (arrayStrings.length == 0) { 119 | buffer.append(c); 120 | convert(n + 1, str); 121 | } else if (arrayStrings.length == 1) { 122 | if (isSimple) { 123 | if (!"".equals(arrayStrings[0])) { 124 | buffer.append(" " + arrayStrings[0].charAt(0) + " "); 125 | } 126 | } else { 127 | buffer.append(" " + arrayStrings[0] + " "); 128 | } 129 | convert(n + 1, str); 130 | } else { 131 | int len; 132 | for (int i = 0; i < arrayStrings.length; i++) { 133 | len = buffer.length(); 134 | if (isSimple) { 135 | if (!"".equals(arrayStrings[i])) { 136 | buffer.append(" " + arrayStrings[i].charAt(0) + " "); 137 | } 138 | } else { 139 | buffer.append(" " + arrayStrings[i] + " "); 140 | } 141 | convert(n + 1, str); 142 | buffer.delete(len, buffer.length()); 143 | } 144 | } 145 | } else { 146 | // 任意非数字与字母的char均当成空格 147 | if (Character.isDigit(c) || TextUtil.isEnglishCharactor(c)) { 148 | buffer.append(c); 149 | } else if (c == '#' || c == '*') { 150 | buffer.append(" ").append(c).append(" "); 151 | } else { 152 | buffer.append(" "); 153 | } 154 | convert(n + 1, str); 155 | } 156 | } 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/util/IconContainer.java: -------------------------------------------------------------------------------- 1 | package net.nashlegend.quickid.util; 2 | 3 | import java.io.File; 4 | import java.lang.ref.SoftReference; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | import net.nashlegend.quickid.model.Contact; 8 | 9 | 10 | import android.graphics.Bitmap; 11 | 12 | public class IconContainer { 13 | private final static ConcurrentHashMap> mCachedIcons = new ConcurrentHashMap>(); 14 | 15 | public static Bitmap get(String path) { 16 | Bitmap bm = null; 17 | if (mCachedIcons.containsKey(path)) { 18 | bm = mCachedIcons.get(path).get(); 19 | } 20 | return bm; 21 | } 22 | 23 | public static Bitmap get(Contact contact) { 24 | return get(contact.getPhotoUri()); 25 | } 26 | 27 | public static void put(String path, Bitmap bm) { 28 | SoftReference soft = new SoftReference(bm); 29 | mCachedIcons.put(path, soft); 30 | } 31 | 32 | public static void put(Contact contact, Bitmap bm) { 33 | put(contact.getPhotoUri(), bm); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/util/PinnedHeaderListView.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.util; 3 | 4 | import net.nashlegend.quickid.view.SplitterView; 5 | 6 | import android.content.Context; 7 | import android.graphics.Canvas; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.*; 12 | import android.widget.AbsListView.OnScrollListener; 13 | 14 | public class PinnedHeaderListView extends ListView implements OnScrollListener { 15 | 16 | private OnScrollListener mOnScrollListener; 17 | 18 | public static interface PinnedHeaderAdapter { 19 | public boolean isSectionHeader(int position); 20 | 21 | public int getSectionForPosition(int position); 22 | 23 | public View getSectionHeaderView(int section, View convertView, ViewGroup parent); 24 | 25 | public int getSectionHeaderViewType(int section); 26 | 27 | public int getCount(); 28 | 29 | } 30 | 31 | private PinnedHeaderAdapter mAdapter; 32 | private View mCurrentHeader; 33 | private int mCurrentHeaderViewType = 0; 34 | private float mHeaderOffset; 35 | private boolean mShouldPin = true; 36 | private int mCurrentSection = 0; 37 | private int mWidthMode; 38 | private int mHeightMode; 39 | 40 | public PinnedHeaderListView(Context context) { 41 | super(context); 42 | super.setOnScrollListener(this); 43 | } 44 | 45 | public PinnedHeaderListView(Context context, AttributeSet attrs) { 46 | super(context, attrs); 47 | super.setOnScrollListener(this); 48 | } 49 | 50 | public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) { 51 | super(context, attrs, defStyle); 52 | super.setOnScrollListener(this); 53 | } 54 | 55 | public void setPinHeaders(boolean shouldPin) { 56 | mShouldPin = shouldPin; 57 | } 58 | 59 | @Override 60 | public void setAdapter(ListAdapter adapter) { 61 | mCurrentHeader = null; 62 | mAdapter = (PinnedHeaderAdapter) adapter; 63 | super.setAdapter(adapter); 64 | } 65 | 66 | @Override 67 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 68 | int totalItemCount) { 69 | if (mOnScrollListener != null) { 70 | mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); 71 | } 72 | 73 | if (mAdapter == null || mAdapter.getCount() == 0 || !mShouldPin 74 | || (firstVisibleItem < getHeaderViewsCount())) { 75 | mCurrentHeader = null; 76 | mHeaderOffset = 0.0f; 77 | for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) { 78 | View header = getChildAt(i); 79 | if (header != null) { 80 | header.setVisibility(VISIBLE); 81 | } 82 | } 83 | return; 84 | } 85 | 86 | firstVisibleItem -= getHeaderViewsCount(); 87 | 88 | int section = mAdapter.getSectionForPosition(firstVisibleItem); 89 | int viewType = mAdapter.getSectionHeaderViewType(section); 90 | mCurrentHeader = getSectionHeaderView(section, mCurrentHeaderViewType != viewType ? null 91 | : mCurrentHeader); 92 | ensurePinnedHeaderLayout(mCurrentHeader); 93 | mCurrentHeaderViewType = viewType; 94 | mHeaderOffset = 0.0f; 95 | for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) { 96 | if (mAdapter.isSectionHeader(i)) { 97 | View header = getChildAt(i - firstVisibleItem); 98 | float headerTop = header.getTop(); 99 | float pinnedHeaderHeight = mCurrentHeader.getMeasuredHeight(); 100 | header.setVisibility(VISIBLE); 101 | if (pinnedHeaderHeight >= headerTop && headerTop >= 0) { 102 | mHeaderOffset = headerTop - header.getHeight(); 103 | } else if (headerTop < 0) { 104 | header.setVisibility(INVISIBLE); 105 | } 106 | } 107 | } 108 | invalidate(); 109 | } 110 | 111 | @Override 112 | public void onScrollStateChanged(AbsListView view, int scrollState) { 113 | if (mOnScrollListener != null) { 114 | mOnScrollListener.onScrollStateChanged(view, scrollState); 115 | } 116 | } 117 | 118 | private View getSectionHeaderView(int section, View oldView) { 119 | boolean shouldLayout = section != mCurrentSection || oldView == null; 120 | 121 | View view = mAdapter.getSectionHeaderView(section, oldView, this); 122 | if (shouldLayout) { 123 | // a new section, thus a new header. We should lay it out again 124 | ensurePinnedHeaderLayout(view); 125 | mCurrentSection = section; 126 | } 127 | return view; 128 | } 129 | 130 | private void ensurePinnedHeaderLayout(View header) { 131 | if (header.isLayoutRequested()) { 132 | int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), mWidthMode); 133 | 134 | int heightSpec; 135 | ViewGroup.LayoutParams layoutParams = header.getLayoutParams(); 136 | if (layoutParams != null && layoutParams.height > 0) { 137 | heightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY); 138 | } else { 139 | heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 140 | } 141 | header.measure(widthSpec, heightSpec); 142 | header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight()); 143 | } 144 | } 145 | 146 | @Override 147 | protected void dispatchDraw(Canvas canvas) { 148 | super.dispatchDraw(canvas); 149 | if (mAdapter == null || !mShouldPin || mCurrentHeader == null) 150 | return; 151 | int saveCount = canvas.save(); 152 | canvas.translate(0, mHeaderOffset); 153 | canvas.clipRect(0, 0, mCurrentHeader.getWidth(), mCurrentHeader.getMeasuredHeight()); // needed 154 | mCurrentHeader.draw(canvas); 155 | canvas.restoreToCount(saveCount); 156 | } 157 | 158 | @Override 159 | public void setOnScrollListener(OnScrollListener l) { 160 | mOnScrollListener = l; 161 | } 162 | 163 | @Override 164 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 165 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 166 | 167 | mWidthMode = MeasureSpec.getMode(widthMeasureSpec); 168 | mHeightMode = MeasureSpec.getMode(heightMeasureSpec); 169 | } 170 | 171 | public void setOnItemClickListener(PinnedHeaderListView.OnItemClickListener listener) { 172 | super.setOnItemClickListener(listener); 173 | } 174 | 175 | public static abstract class OnItemClickListener implements AdapterView.OnItemClickListener { 176 | @Override 177 | public void onItemClick(AdapterView adapterView, View view, int rawPosition, long id) { 178 | SectionedBaseAdapter adapter; 179 | if (adapterView.getAdapter().getClass().equals(HeaderViewListAdapter.class)) { 180 | HeaderViewListAdapter wrapperAdapter = (HeaderViewListAdapter) adapterView 181 | .getAdapter(); 182 | adapter = (SectionedBaseAdapter) wrapperAdapter.getWrappedAdapter(); 183 | } else { 184 | adapter = (SectionedBaseAdapter) adapterView.getAdapter(); 185 | } 186 | int section = adapter.getSectionForPosition(rawPosition); 187 | int position = adapter.getPositionInSectionForPosition(rawPosition); 188 | 189 | if (position == -1) { 190 | onSectionClick(adapterView, view, section, id); 191 | } else { 192 | onItemClick(adapterView, view, section, position, id); 193 | } 194 | } 195 | 196 | public abstract void onItemClick(AdapterView adapterView, View view, int section, 197 | int position, long id); 198 | 199 | public abstract void onSectionClick(AdapterView adapterView, View view, int section, 200 | long id); 201 | 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/util/SectionedBaseAdapter.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.util; 3 | 4 | import net.nashlegend.quickid.util.PinnedHeaderListView.PinnedHeaderAdapter; 5 | import android.annotation.SuppressLint; 6 | import android.util.SparseArray; 7 | import android.util.SparseIntArray; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.BaseAdapter; 11 | 12 | public abstract class SectionedBaseAdapter extends BaseAdapter implements 13 | PinnedHeaderAdapter { 14 | 15 | private static int HEADER_VIEW_TYPE = 0; 16 | private static int ITEM_VIEW_TYPE = 0; 17 | // 不能使用SparseIntArray,它在初始化的时候默认就添加了12个{0,0}键值对,-_-/// 18 | private SparseArray mSectionPositionCache; 19 | private SparseArray mSectionCache; 20 | private SparseArray mSectionCountCache; 21 | private int mCount; 22 | private int mSectionCount; 23 | 24 | @SuppressLint("UseSparseArrays") 25 | public SectionedBaseAdapter() { 26 | super(); 27 | mSectionCache = new SparseArray(); 28 | mSectionPositionCache = new SparseArray(); 29 | mSectionCountCache = new SparseArray(); 30 | mCount = -1; 31 | mSectionCount = -1; 32 | } 33 | 34 | @Override 35 | public void notifyDataSetChanged() { 36 | mSectionCache.clear(); 37 | mSectionPositionCache.clear(); 38 | mSectionCountCache.clear(); 39 | mCount = -1; 40 | mSectionCount = -1; 41 | super.notifyDataSetChanged(); 42 | } 43 | 44 | @Override 45 | public void notifyDataSetInvalidated() { 46 | mSectionCache.clear(); 47 | mSectionPositionCache.clear(); 48 | mSectionCountCache.clear(); 49 | mCount = -1; 50 | mSectionCount = -1; 51 | super.notifyDataSetInvalidated(); 52 | } 53 | 54 | @Override 55 | public final int getCount() { 56 | if (mCount >= 0) { 57 | return mCount; 58 | } 59 | int count = 0; 60 | for (int i = 0; i < internalGetSectionCount(); i++) { 61 | count += internalGetCountForSection(i); 62 | count++; // for the header view 63 | } 64 | mCount = count; 65 | return count; 66 | } 67 | 68 | @Override 69 | public final Object getItem(int position) { 70 | return getItem(getSectionForPosition(position), getPositionInSectionForPosition(position)); 71 | } 72 | 73 | @Override 74 | public final long getItemId(int position) { 75 | return getItemId(getSectionForPosition(position), getPositionInSectionForPosition(position)); 76 | } 77 | 78 | @Override 79 | public final View getView(int position, View convertView, ViewGroup parent) { 80 | if (isSectionHeader(position)) { 81 | return getSectionHeaderView(getSectionForPosition(position), convertView, parent); 82 | } 83 | return getItemView(getSectionForPosition(position), 84 | getPositionInSectionForPosition(position), convertView, parent); 85 | } 86 | 87 | @Override 88 | public final int getItemViewType(int position) { 89 | if (isSectionHeader(position)) { 90 | return getItemViewTypeCount() 91 | + getSectionHeaderViewType(getSectionForPosition(position)); 92 | } 93 | return getItemViewType(getSectionForPosition(position), 94 | getPositionInSectionForPosition(position)); 95 | } 96 | 97 | @Override 98 | public final int getViewTypeCount() { 99 | return getItemViewTypeCount() + getSectionHeaderViewTypeCount(); 100 | } 101 | 102 | public final int getSectionForPosition(int position) { 103 | Integer cachedSection = mSectionCache.get(position); 104 | if (cachedSection != null) { 105 | return cachedSection; 106 | } 107 | int sectionStart = 0; 108 | for (int i = 0; i < internalGetSectionCount(); i++) { 109 | int sectionCount = internalGetCountForSection(i); 110 | int sectionEnd = sectionStart + sectionCount + 1; 111 | if (position >= sectionStart && position < sectionEnd) { 112 | mSectionCache.put(position, i); 113 | return i; 114 | } 115 | sectionStart = sectionEnd; 116 | } 117 | return 0; 118 | } 119 | 120 | public int getPositionInSectionForPosition(int position) { 121 | Integer cachedPosition = mSectionPositionCache.get(position); 122 | if (cachedPosition != null) { 123 | return cachedPosition; 124 | } 125 | int sectionStart = 0; 126 | for (int i = 0; i < internalGetSectionCount(); i++) { 127 | int sectionCount = internalGetCountForSection(i); 128 | int sectionEnd = sectionStart + sectionCount + 1; 129 | if (position >= sectionStart && position < sectionEnd) { 130 | int positionInSection = position - sectionStart - 1; 131 | mSectionPositionCache.put(position, positionInSection); 132 | return positionInSection; 133 | } 134 | sectionStart = sectionEnd; 135 | } 136 | return 0; 137 | } 138 | 139 | public final boolean isSectionHeader(int position) { 140 | int sectionStart = 0; 141 | for (int i = 0; i < internalGetSectionCount(); i++) { 142 | if (position == sectionStart) { 143 | return true; 144 | } else if (position < sectionStart) { 145 | return false; 146 | } 147 | sectionStart += internalGetCountForSection(i) + 1; 148 | } 149 | return false; 150 | } 151 | 152 | public int getItemViewType(int section, int position) { 153 | return ITEM_VIEW_TYPE; 154 | } 155 | 156 | public int getItemViewTypeCount() { 157 | return 1; 158 | } 159 | 160 | public int getSectionHeaderViewType(int section) { 161 | return HEADER_VIEW_TYPE; 162 | } 163 | 164 | public int getSectionHeaderViewTypeCount() { 165 | return 1; 166 | } 167 | 168 | public abstract Object getItem(int section, int position); 169 | 170 | public abstract long getItemId(int section, int position); 171 | 172 | public abstract int getSectionCount(); 173 | 174 | public abstract int getCountForSection(int section); 175 | 176 | public abstract View getItemView(int section, int position, View convertView, ViewGroup parent); 177 | 178 | public abstract View getSectionHeaderView(int section, View convertView, ViewGroup parent); 179 | 180 | private int internalGetCountForSection(int section) { 181 | Integer cachedSectionCount = mSectionCountCache.get(section); 182 | if (cachedSectionCount != null) { 183 | return cachedSectionCount; 184 | } 185 | int sectionCount = getCountForSection(section); 186 | mSectionCountCache.put(section, sectionCount); 187 | return sectionCount; 188 | } 189 | 190 | private int internalGetSectionCount() { 191 | if (mSectionCount >= 0) { 192 | return mSectionCount; 193 | } 194 | mSectionCount = getSectionCount(); 195 | return mSectionCount; 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/util/SpecialCharSequenceMgr.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.nashlegend.quickid.util; 18 | 19 | import android.content.Context; 20 | import android.widget.EditText; 21 | 22 | /** 23 | * Helper class to listen for some magic character sequences 24 | * that are handled specially by the dialer. 25 | * 26 | * Note the Phone app also handles these sequences too (in a couple of 27 | * relatively obscure places in the UI), so there's a separate version of 28 | * this class under apps/Phone. 29 | * 30 | * TODO: there's lots of duplicated code between this class and the 31 | * corresponding class under apps/Phone. Let's figure out a way to 32 | * unify these two classes (in the framework? in a common shared library?) 33 | */ 34 | public class SpecialCharSequenceMgr { 35 | 36 | /** This class is never instantiated. */ 37 | private SpecialCharSequenceMgr() { 38 | } 39 | 40 | public static boolean handleChars(Context context, String input, EditText textField) { 41 | return handleChars(context, input, false, textField); 42 | } 43 | 44 | static boolean handleChars(Context context, String input) { 45 | return handleChars(context, input, false, null); 46 | } 47 | 48 | static boolean handleChars(Context context, String input, boolean useSystemWindow, 49 | EditText textField) { 50 | return false; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/view/CallLogView.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.view; 3 | 4 | import java.io.InputStream; 5 | import java.text.DateFormat; 6 | import java.text.SimpleDateFormat; 7 | import java.util.ArrayList; 8 | import java.util.Calendar; 9 | import java.util.Date; 10 | import java.util.Iterator; 11 | 12 | import net.nashlegend.quickid.AppApplication; 13 | import net.nashlegend.quickid.model.Contact; 14 | import net.nashlegend.quickid.model.Contact.PointPair; 15 | import net.nashlegend.quickid.util.Consts; 16 | import net.nashlegend.quickid.util.ContactHelper; 17 | import net.nashlegend.quickid.util.IconContainer; 18 | 19 | import net.nashlegend.quickid.R; 20 | 21 | import android.annotation.SuppressLint; 22 | import android.app.AlertDialog; 23 | import android.content.ContentUris; 24 | import android.content.Context; 25 | import android.content.DialogInterface; 26 | import android.content.Intent; 27 | import android.content.res.TypedArray; 28 | import android.graphics.Bitmap; 29 | import android.graphics.BitmapFactory; 30 | import android.graphics.Canvas; 31 | import android.graphics.Color; 32 | import android.graphics.Rect; 33 | import android.net.Uri; 34 | import android.os.AsyncTask; 35 | import android.os.Handler; 36 | import android.os.AsyncTask.Status; 37 | import android.provider.CallLog.Calls; 38 | import android.provider.ContactsContract.Contacts; 39 | import android.text.Spannable; 40 | import android.text.SpannableStringBuilder; 41 | import android.text.TextUtils; 42 | import android.text.style.ForegroundColorSpan; 43 | import android.util.AttributeSet; 44 | import android.view.LayoutInflater; 45 | import android.view.MotionEvent; 46 | import android.view.View; 47 | import android.view.View.OnTouchListener; 48 | import android.widget.FrameLayout; 49 | import android.widget.ImageButton; 50 | import android.widget.ImageView; 51 | import android.widget.QuickContactBadge; 52 | import android.widget.TextView; 53 | 54 | @SuppressLint("SimpleDateFormat") 55 | public class CallLogView extends FrameLayout { 56 | 57 | private Contact contact; 58 | private QuickContactBadge badge; 59 | private TextView nameTextView; 60 | private TextView phoneTextView; 61 | private TextView dateTextView; 62 | private IconLoadTask task; 63 | private ImageView typeImageView; 64 | private ImageButton smsButton; 65 | private View number_layoutView; 66 | 67 | public CallLogView(Context context) { 68 | super(context); 69 | LayoutInflater inflater = (LayoutInflater) context 70 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 71 | inflater.inflate(R.layout.layout_calllog_item, this); 72 | badge = (QuickContactBadge) findViewById(R.id.badge_contact_item); 73 | nameTextView = (TextView) findViewById(R.id.text_contact_name); 74 | phoneTextView = (TextView) findViewById(R.id.text_contact_phone); 75 | typeImageView = (ImageView) findViewById(R.id.imageview_call_type); 76 | dateTextView = (TextView) findViewById(R.id.textview_call_date); 77 | smsButton = (ImageButton) findViewById(R.id.button_send_sms); 78 | number_layoutView = findViewById(R.id.layout_phone_numbers); 79 | smsButton.setOnClickListener(onClickListener); 80 | number_layoutView.setClickable(true); 81 | number_layoutView.setOnTouchListener(new OnShortLongClickListener()); 82 | } 83 | 84 | public CallLogView(Context context, AttributeSet attrs) { 85 | super(context, attrs); 86 | } 87 | 88 | public CallLogView(Context context, AttributeSet attrs, int defStyle) { 89 | super(context, attrs, defStyle); 90 | } 91 | 92 | public void build() { 93 | switch (contact.Last_Contact_Call_Type) { 94 | case Calls.OUTGOING_TYPE: 95 | typeImageView 96 | .setImageResource(R.drawable.ic_call_outgoing_holo_dark); 97 | break; 98 | case Calls.INCOMING_TYPE: 99 | typeImageView 100 | .setImageResource(R.drawable.ic_call_incoming_holo_dark); 101 | break; 102 | case Calls.MISSED_TYPE: 103 | typeImageView.setImageResource(R.drawable.ic_call_missed_holo_dark); 104 | break; 105 | 106 | default: 107 | break; 108 | } 109 | dateTextView.setText(getDateDescription(contact.Last_Time_Contacted)); 110 | String nameString = contact.getName(); 111 | nameTextView.setText(nameString); 112 | if (TextUtils.isEmpty(contact.getName())) { 113 | nameTextView.setText(contact.Last_Contact_Number); 114 | phoneTextView.setText(""); 115 | } else { 116 | nameTextView.setText(contact.getName()); 117 | phoneTextView.setText(contact.Last_Contact_Number); 118 | } 119 | if (!TextUtils.isEmpty(contact.getLookupKey())) { 120 | badge.assignContactUri(Contacts.getLookupUri( 121 | contact.getContactId(), contact.getLookupKey())); 122 | } else { 123 | badge.assignContactUri(null); 124 | } 125 | loadAvatar(); 126 | } 127 | 128 | private String getDateDescription(long mills) { 129 | String dateString = ""; 130 | Calendar date = Calendar.getInstance(); 131 | date.setTimeInMillis(mills); 132 | 133 | Calendar date2 = Calendar.getInstance(); 134 | date2.setTimeInMillis(System.currentTimeMillis()); 135 | 136 | DateFormat format; 137 | 138 | if (mills / 86400000 != System.currentTimeMillis() / 86400000) { 139 | // 如果不是今天 140 | if (date.get(Calendar.YEAR) == date2.get(Calendar.YEAR)) { 141 | // 若是同一年 142 | format = new SimpleDateFormat("M月d日 HH:mm:ss"); 143 | dateString = format.format(date.getTime()); 144 | } else { 145 | // 若不是同一年 146 | format = new SimpleDateFormat("yyyy年M月d日 HH:mm:ss"); 147 | dateString = format.format(date.getTime()); 148 | } 149 | } else { 150 | long diff = System.currentTimeMillis() - mills; 151 | if (diff > 1800000) { 152 | // 如果半小时以前 153 | format = new SimpleDateFormat("今天 HH:mm:ss"); 154 | dateString = format.format(date.getTime()); 155 | } else if (diff >= 60000) { 156 | // 一分钟到半小时之前 157 | dateString = (diff / 60000) + "分钟前"; 158 | } else { 159 | // 一分钟以内 160 | dateString = (diff / 1000) + "秒前"; 161 | } 162 | } 163 | return dateString; 164 | } 165 | 166 | private void loadAvatar() { 167 | badge.setImageResource(R.drawable.ic_contact_picture_holo_light); 168 | if (!TextUtils.isEmpty(contact.getPhotoUri())) { 169 | if (task != null && task.getStatus() == Status.RUNNING) { 170 | task.cancel(true); 171 | } 172 | Bitmap bmp = IconContainer.get(contact); 173 | if (bmp == null) { 174 | task = new IconLoadTask(); 175 | task.execute(contact); 176 | } else { 177 | badge.setImageBitmap(bmp); 178 | } 179 | } else { 180 | setDefaultAvatar(); 181 | } 182 | } 183 | 184 | private static TypedArray sColors; 185 | private static int sDefaultColor; 186 | private static final int NUM_OF_TILE_COLORS = 8; 187 | 188 | @SuppressLint("Recycle") 189 | private void setDefaultAvatar() { 190 | if (sColors == null) { 191 | sColors = getResources().obtainTypedArray( 192 | R.array.letter_tile_colors); 193 | sDefaultColor = getResources().getColor( 194 | R.color.letter_tile_default_color); 195 | } 196 | badge.setBackgroundColor(pickColor(contact.getName())); 197 | badge.setImageResource(R.drawable.ic_list_item_avatar); 198 | } 199 | 200 | private int pickColor(final String identifier) { 201 | if (TextUtils.isEmpty(identifier)) { 202 | return sDefaultColor; 203 | } 204 | final int color = Math.abs(identifier.hashCode()) % NUM_OF_TILE_COLORS; 205 | return sColors.getColor(color, sDefaultColor); 206 | } 207 | 208 | public void setContact(Contact contact) { 209 | this.contact = contact; 210 | } 211 | 212 | public Contact getContact() { 213 | return contact; 214 | } 215 | 216 | class IconLoadTask extends AsyncTask { 217 | 218 | Contact originalContact; 219 | 220 | @Override 221 | protected Bitmap doInBackground(Contact... params) { 222 | originalContact = params[0]; 223 | Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 224 | contact.getContactId()); 225 | InputStream input = Contacts.openContactPhotoInputStream( 226 | AppApplication.getApplicationContentResolver(), uri); 227 | Bitmap bmp = BitmapFactory.decodeStream(input); 228 | if (bmp != null) { 229 | IconContainer.put(originalContact, bmp); 230 | } 231 | return bmp; 232 | } 233 | 234 | @Override 235 | protected void onPostExecute(Bitmap result) { 236 | if (contact.equals(this.originalContact)) { 237 | if (result != null) { 238 | badge.setImageBitmap(result); 239 | } else { 240 | badge.setImageResource(R.drawable.ic_list_item_avatar); 241 | } 242 | } 243 | super.onPostExecute(result); 244 | } 245 | } 246 | 247 | CharSequence[] items = { 248 | "拨打电话", "发送短信", "删除记录", "查看联系人", "删除全部记录" 249 | }; 250 | CharSequence[] itemsAnonymous = { 251 | "拨打电话", "发送短信", "删除记录", "添加到通讯录", "删除全部记录" 252 | }; 253 | static final int MAKE_PHONE_CALL = 0; 254 | static final int SEND_SMS = 1; 255 | static final int DELETE_LOG = 2; 256 | static final int SEE_OR_ADD_CONTAC = 3; 257 | static final int DELETE_ALL = 4; 258 | 259 | private void onLongClick() { 260 | CharSequence[] clickChars; 261 | if (contact.getContactId() > 0) { 262 | clickChars = items; 263 | } else { 264 | clickChars = itemsAnonymous; 265 | } 266 | new AlertDialog.Builder(getContext()).setTitle("") 267 | .setItems(clickChars, new DialogInterface.OnClickListener() { 268 | 269 | @Override 270 | public void onClick(DialogInterface dialog, int which) { 271 | switch (which) { 272 | case MAKE_PHONE_CALL: 273 | ContactHelper.makePhoneCall(contact.Last_Contact_Number); 274 | break; 275 | case SEND_SMS: 276 | ContactHelper.sendSMS(contact.Last_Contact_Number); 277 | break; 278 | case DELETE_LOG: 279 | if (ContactHelper 280 | .deleteCallLogByNumber(contact.Last_Contact_Number) > 0) { 281 | Intent intent = new Intent(); 282 | intent.setAction(Consts.Action_Delete_One_Call_Log); 283 | intent.putExtra(Consts.Extra_Calllog_Number, 284 | contact.Last_Contact_Number); 285 | getContext().sendBroadcast(intent); 286 | } 287 | break; 288 | case SEE_OR_ADD_CONTAC: 289 | // id从0开始递增的 290 | if (contact.getContactId() > 0L) { 291 | ContactHelper.openContactDetail(contact.getContactId()); 292 | }else { 293 | ContactHelper.addContact(contact.Last_Contact_Number); 294 | } 295 | break; 296 | case DELETE_ALL: 297 | ContactHelper.clearCallLogs(); 298 | Intent intent = new Intent(); 299 | intent.setAction(Consts.Action_Clear_Call_Log); 300 | getContext().sendBroadcast(intent); 301 | break; 302 | default: 303 | break; 304 | } 305 | } 306 | }).create().show(); 307 | } 308 | 309 | OnClickListener onClickListener = new OnClickListener() { 310 | 311 | @Override 312 | public void onClick(View v) { 313 | switch (v.getId()) { 314 | case R.id.button_send_sms: 315 | ContactHelper.sendSMS(contact.Last_Contact_Number); 316 | break; 317 | default: 318 | break; 319 | } 320 | } 321 | }; 322 | 323 | class OnShortLongClickListener implements OnTouchListener { 324 | long longDura = 800L; 325 | long shortDura = 300L; 326 | long startTime = 0L; 327 | Handler handler = new Handler(); 328 | Runnable longPressRunnable = new Runnable() { 329 | public void run() { 330 | onLongClick(); 331 | } 332 | }; 333 | 334 | @SuppressLint("ClickableViewAccessibility") 335 | @Override 336 | public boolean onTouch(View v, MotionEvent event) { 337 | switch (event.getAction()) { 338 | case MotionEvent.ACTION_DOWN: 339 | startTime = System.currentTimeMillis(); 340 | handler.removeCallbacks(longPressRunnable); 341 | handler.postDelayed(longPressRunnable, longDura); 342 | break; 343 | case MotionEvent.ACTION_UP: 344 | handler.removeCallbacks(longPressRunnable); 345 | if (System.currentTimeMillis() - startTime < shortDura) { 346 | ContactHelper.makePhoneCall(contact.Last_Contact_Number); 347 | } 348 | break; 349 | case MotionEvent.ACTION_CANCEL: 350 | handler.removeCallbacks(longPressRunnable); 351 | break; 352 | 353 | default: 354 | break; 355 | } 356 | return false; 357 | } 358 | } 359 | 360 | } 361 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/view/ContactView.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.view; 3 | 4 | import java.io.InputStream; 5 | import java.util.ArrayList; 6 | import java.util.Iterator; 7 | 8 | import net.nashlegend.quickid.AppApplication; 9 | import net.nashlegend.quickid.model.Contact; 10 | import net.nashlegend.quickid.model.Contact.PhoneStruct; 11 | import net.nashlegend.quickid.model.Contact.PointPair; 12 | import net.nashlegend.quickid.util.Consts; 13 | import net.nashlegend.quickid.util.ContactHelper; 14 | import net.nashlegend.quickid.util.IconContainer; 15 | 16 | import net.nashlegend.quickid.R; 17 | 18 | import android.annotation.SuppressLint; 19 | import android.app.AlertDialog; 20 | import android.content.ContentUris; 21 | import android.content.Context; 22 | import android.content.DialogInterface; 23 | import android.content.Intent; 24 | import android.content.res.TypedArray; 25 | import android.graphics.Bitmap; 26 | import android.graphics.BitmapFactory; 27 | import android.graphics.Canvas; 28 | import android.graphics.Color; 29 | import android.graphics.Rect; 30 | import android.net.Uri; 31 | import android.os.AsyncTask; 32 | import android.os.Handler; 33 | import android.os.AsyncTask.Status; 34 | import android.provider.ContactsContract.Contacts; 35 | import android.text.Spannable; 36 | import android.text.SpannableStringBuilder; 37 | import android.text.TextUtils; 38 | import android.text.style.ForegroundColorSpan; 39 | import android.util.AttributeSet; 40 | import android.view.LayoutInflater; 41 | import android.view.MotionEvent; 42 | import android.view.View; 43 | import android.widget.FrameLayout; 44 | import android.widget.ImageButton; 45 | import android.widget.LinearLayout; 46 | import android.widget.QuickContactBadge; 47 | import android.widget.TextView; 48 | 49 | public class ContactView extends FrameLayout { 50 | 51 | private Contact contact; 52 | private QuickContactBadge badge; 53 | private TextView nameTextView; 54 | private TextView pinyinTextView; 55 | private TextView phoneTextView; 56 | private LinearLayout phoneLayout; 57 | private IconLoadTask task; 58 | public int Display_Mode = 0; 59 | private ImageButton smsButton; 60 | private LinearLayout phoneViews; 61 | public static final int Display_Mode_Recent = 1; 62 | public static final int Display_Mode_Search = 2; 63 | public static final int Display_Mode_Display = 3; 64 | 65 | public ContactView(Context context, int display) { 66 | super(context); 67 | LayoutInflater inflater = (LayoutInflater) context 68 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 69 | inflater.inflate(R.layout.layout_contact_item, this); 70 | badge = (QuickContactBadge) findViewById(R.id.badge_contact_item); 71 | smsButton = (ImageButton) findViewById(R.id.button_send_sms); 72 | nameTextView = (TextView) findViewById(R.id.text_contact_name); 73 | pinyinTextView = (TextView) findViewById(R.id.text_contact_pinyin); 74 | phoneTextView = (TextView) findViewById(R.id.text_contact_phone); 75 | phoneViews = (LinearLayout) findViewById(R.id.layout_more_phones); 76 | phoneLayout = (LinearLayout) findViewById(R.id.layout_phone_numbers); 77 | this.Display_Mode = display; 78 | } 79 | 80 | public ContactView(Context context, AttributeSet attrs) { 81 | super(context, attrs); 82 | } 83 | 84 | public ContactView(Context context, AttributeSet attrs, int defStyle) { 85 | super(context, attrs, defStyle); 86 | } 87 | 88 | public void build() { 89 | phoneViews.removeAllViews(); 90 | boolean shouldDisplayMorePhones = true; 91 | phoneTextView.setText(""); 92 | pinyinTextView.setText(""); 93 | badge.assignContactUri(Contacts.getLookupUri(contact.getContactId(), 94 | contact.getLookupKey())); 95 | String nameString = contact.getName(); 96 | String phoneString = ""; 97 | if (contact.getPhones().size() > 0) { 98 | phoneString = contact.getPhones().get(0).phoneNumber; 99 | } 100 | nameTextView.setText(nameString); 101 | switch (Display_Mode) { 102 | case Display_Mode_Display: 103 | smsButton.setVisibility(View.GONE); 104 | phoneTextView.setVisibility(View.GONE); 105 | shouldDisplayMorePhones = false; 106 | break; 107 | case Display_Mode_Search: 108 | smsButton.setVisibility(View.VISIBLE); 109 | phoneTextView.setVisibility(View.VISIBLE); 110 | phoneTextView.setText(phoneString); 111 | if (contact.matchValue.nameIndex < 0 112 | || contact.matchValue.nameIndex > contact.fullNamesString 113 | .size() - 1) { 114 | break; 115 | } 116 | if (contact.matchValue.matchLevel == Contact.Level_Complete) { 117 | if (contact.matchValue.matchType == Contact.Match_Type_Name) { 118 | String str = contact.fullNamesString.get( 119 | contact.matchValue.nameIndex).replaceAll(" ", ""); 120 | SpannableStringBuilder builder = new SpannableStringBuilder( 121 | str); 122 | ForegroundColorSpan redSpan = new ForegroundColorSpan( 123 | Color.RED); 124 | builder.setSpan(redSpan, 0, str.length(), 125 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 126 | pinyinTextView.setText(builder); 127 | } else { 128 | shouldDisplayMorePhones = false; 129 | String str = contact.getPhones().get( 130 | contact.matchValue.nameIndex).phoneNumber; 131 | SpannableStringBuilder builder = new SpannableStringBuilder( 132 | str); 133 | ForegroundColorSpan redSpan = new ForegroundColorSpan( 134 | Color.RED); 135 | builder.setSpan(redSpan, 0, str.length(), 136 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 137 | phoneTextView.setText(builder); 138 | } 139 | } else if (contact.matchValue.matchLevel == Contact.Level_Headless) { 140 | shouldDisplayMorePhones = false; 141 | String str = contact.getPhones().get( 142 | contact.matchValue.nameIndex).phoneNumber; 143 | SpannableStringBuilder builder = new SpannableStringBuilder(str); 144 | ForegroundColorSpan redSpan = new ForegroundColorSpan(Color.RED); 145 | builder.setSpan(redSpan, 146 | contact.matchValue.pairs.get(0).strIndex, 147 | contact.matchValue.pairs.get(0).strIndex 148 | + contact.matchValue.reg.length(), 149 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 150 | phoneTextView.setText(builder); 151 | for (int i = 1; i < contact.matchValue.pairs.size(); i++) { 152 | int idx = contact.matchValue.pairs.get(i).listIndex; 153 | PhoneStruct phoneStruct = contact.getPhones().get(idx); 154 | PhoneView phoneView = new PhoneView(getContext()); 155 | phoneView.setPhone(phoneStruct, contact.matchValue.reg); 156 | phoneViews.addView(phoneView); 157 | } 158 | } else { 159 | String str = contact.fullNamesString.get( 160 | contact.matchValue.nameIndex).replaceAll(" ", ""); 161 | ArrayList pa = getColoredString( 162 | contact.fullNameNumber 163 | .get(contact.matchValue.nameIndex), 164 | contact.matchValue.pairs, "#FF0000"); 165 | SpannableStringBuilder builder = new SpannableStringBuilder(str); 166 | for (Iterator iterator = pa.iterator(); iterator 167 | .hasNext();) { 168 | PointPair pointPair = iterator.next(); 169 | builder.setSpan(new ForegroundColorSpan(Color.RED), 170 | pointPair.listIndex, pointPair.strIndex, 171 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 172 | } 173 | pinyinTextView.setText(builder); 174 | } 175 | break; 176 | 177 | default: 178 | break; 179 | } 180 | if (shouldDisplayMorePhones) { 181 | for (int i = 1; i < contact.getPhones().size(); i++) { 182 | PhoneStruct phoneStruct = contact.getPhones().get(i); 183 | PhoneView phoneView = new PhoneView(getContext()); 184 | phoneView.setPhone(phoneStruct); 185 | phoneViews.addView(phoneView); 186 | } 187 | } 188 | loadAvatar(); 189 | 190 | smsButton.setOnClickListener(onClickListener); 191 | phoneLayout.setClickable(true); 192 | phoneLayout.setOnTouchListener(new OnShortLongClickListener()); 193 | } 194 | 195 | private ArrayList getColoredString(ArrayList strings, 196 | ArrayList pairs, String color) { 197 | int k = 0; 198 | int idx = -1; 199 | int crtHead = -1; 200 | int crtTail = -1; 201 | ArrayList ps = new ArrayList(); 202 | for (int i = 0; i < strings.size(); i++) { 203 | String str = strings.get(i); 204 | for (int j = 0; j < str.length() && k < pairs.size(); j++) { 205 | idx++; 206 | if (pairs.get(k).listIndex == i && pairs.get(k).strIndex == j) { 207 | if (crtHead == -1) { 208 | crtHead = idx; 209 | crtTail = idx + 1; 210 | } else { 211 | if (crtTail == idx) { 212 | crtTail = idx + 1; 213 | } 214 | } 215 | k++; 216 | } else { 217 | if (crtHead != -1) { 218 | ps.add(new PointPair(crtHead, crtTail)); 219 | crtHead = -1; 220 | crtTail = -1; 221 | } 222 | } 223 | } 224 | } 225 | if (crtHead != -1) { 226 | ps.add(new PointPair(crtHead, crtTail)); 227 | crtHead = -1; 228 | crtTail = -1; 229 | } 230 | return ps; 231 | } 232 | 233 | private void loadAvatar() { 234 | badge.setImageResource(R.drawable.ic_contact_picture_holo_light); 235 | if (!TextUtils.isEmpty(contact.getPhotoUri())) { 236 | if (task != null && task.getStatus() == Status.RUNNING) { 237 | task.cancel(true); 238 | } 239 | Bitmap bmp = IconContainer.get(contact); 240 | if (bmp == null) { 241 | task = new IconLoadTask(); 242 | task.execute(contact); 243 | } else { 244 | badge.setImageBitmap(bmp); 245 | } 246 | } else { 247 | setDefaultAvatar(); 248 | } 249 | } 250 | 251 | private static TypedArray sColors; 252 | private static int sDefaultColor; 253 | private static final int NUM_OF_TILE_COLORS = 8; 254 | 255 | @SuppressLint("Recycle") 256 | private void setDefaultAvatar() { 257 | if (sColors == null) { 258 | sColors = getResources().obtainTypedArray( 259 | R.array.letter_tile_colors); 260 | sDefaultColor = getResources().getColor( 261 | R.color.letter_tile_default_color); 262 | } 263 | badge.setBackgroundColor(pickColor(contact.getName())); 264 | badge.setImageResource(R.drawable.ic_list_item_avatar); 265 | } 266 | 267 | private int pickColor(final String identifier) { 268 | if (TextUtils.isEmpty(identifier)) { 269 | return sDefaultColor; 270 | } 271 | final int color = Math.abs(identifier.hashCode()) % NUM_OF_TILE_COLORS; 272 | return sColors.getColor(color, sDefaultColor); 273 | } 274 | 275 | public void setContact(Contact contact) { 276 | this.contact = contact; 277 | } 278 | 279 | public Contact getContact() { 280 | return contact; 281 | } 282 | 283 | class IconLoadTask extends AsyncTask { 284 | 285 | Contact originalContact; 286 | 287 | @Override 288 | protected Bitmap doInBackground(Contact... params) { 289 | originalContact = params[0]; 290 | Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 291 | contact.getContactId()); 292 | InputStream input = Contacts.openContactPhotoInputStream( 293 | AppApplication.getApplicationContentResolver(), uri); 294 | Bitmap bmp = BitmapFactory.decodeStream(input); 295 | if (bmp != null) { 296 | IconContainer.put(originalContact, bmp); 297 | } 298 | return bmp; 299 | } 300 | 301 | @Override 302 | protected void onPostExecute(Bitmap result) { 303 | if (contact.equals(this.originalContact)) { 304 | if (result != null) { 305 | badge.setImageBitmap(result); 306 | } else { 307 | badge.setImageResource(R.drawable.ic_list_item_avatar); 308 | } 309 | } 310 | super.onPostExecute(result); 311 | } 312 | } 313 | 314 | CharSequence[] items = { 315 | "拨打电话", "发送短信", "删除联系人", "查看联系人" 316 | }; 317 | static final int MAKE_PHONE_CALL = 0; 318 | static final int SEND_SMS = 1; 319 | static final int DELETE_CONTACT = 2; 320 | static final int SEE_CONTACT = 3; 321 | 322 | private void onLongClick() { 323 | new AlertDialog.Builder(getContext()).setTitle("") 324 | .setItems(items, new DialogInterface.OnClickListener() { 325 | 326 | @Override 327 | public void onClick(DialogInterface dialog, int which) { 328 | switch (which) { 329 | case MAKE_PHONE_CALL: 330 | if (contact.getPhones().size() > 0) { 331 | ContactHelper.makePhoneCall(contact.getPhones() 332 | .get(0).phoneNumber); 333 | } 334 | break; 335 | case SEND_SMS: 336 | if (contact.getPhones().size() > 0) { 337 | ContactHelper.sendSMS(contact.getPhones() 338 | .get(0).phoneNumber); 339 | } 340 | break; 341 | case DELETE_CONTACT: 342 | if (ContactHelper.deleteContactsByID(contact 343 | .getContactId()) > 0) { 344 | Intent intent = new Intent(); 345 | if (Display_Mode == Display_Mode_Display) { 346 | intent.setAction(Consts.Action_Delete_One_Contact_From_All); 347 | } else if (Display_Mode == Display_Mode_Search) { 348 | intent.setAction(Consts.Action_Delete_One_Contact_From_Search); 349 | } 350 | intent.putExtra(Consts.Extra_Contact_ID, 351 | contact.getContactId()); 352 | getContext().sendBroadcast(intent); 353 | } 354 | break; 355 | case SEE_CONTACT: 356 | ContactHelper.openContactDetail(contact 357 | .getContactId()); 358 | break; 359 | default: 360 | break; 361 | } 362 | } 363 | }).create().show(); 364 | } 365 | 366 | OnClickListener onClickListener = new OnClickListener() { 367 | 368 | @Override 369 | public void onClick(View v) { 370 | switch (v.getId()) { 371 | case R.id.button_send_sms: 372 | if (contact.getPhones().size() > 0) { 373 | ContactHelper 374 | .sendSMS(contact.getPhones().get(0).phoneNumber); 375 | } 376 | break; 377 | default: 378 | break; 379 | } 380 | } 381 | }; 382 | 383 | class OnShortLongClickListener implements OnTouchListener { 384 | long longDura = 1000L; 385 | long shortDura = 300L; 386 | long startTime = 0L; 387 | Handler handler = new Handler(); 388 | Runnable longPressRunnable = new Runnable() { 389 | public void run() { 390 | onLongClick(); 391 | } 392 | }; 393 | 394 | @SuppressLint("ClickableViewAccessibility") 395 | @Override 396 | public boolean onTouch(View v, MotionEvent event) { 397 | switch (event.getAction()) { 398 | case MotionEvent.ACTION_DOWN: 399 | startTime = System.currentTimeMillis(); 400 | handler.removeCallbacks(longPressRunnable); 401 | handler.postDelayed(longPressRunnable, longDura); 402 | break; 403 | case MotionEvent.ACTION_UP: 404 | handler.removeCallbacks(longPressRunnable); 405 | if (System.currentTimeMillis() - startTime < shortDura) { 406 | if (Display_Mode == Display_Mode_Display) { 407 | ContactHelper.openContactDetail(contact.getContactId()); 408 | } else if (Display_Mode == Display_Mode_Search) { 409 | ContactHelper 410 | .makePhoneCall(contact.getPhones().get(0).phoneNumber); 411 | } 412 | } 413 | break; 414 | case MotionEvent.ACTION_CANCEL: 415 | handler.removeCallbacks(longPressRunnable); 416 | break; 417 | 418 | default: 419 | break; 420 | } 421 | return false; 422 | } 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/view/DialogNumberPanel.java: -------------------------------------------------------------------------------- 1 | package net.nashlegend.quickid.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.RelativeLayout; 6 | 7 | public class DialogNumberPanel extends RelativeLayout { 8 | 9 | public DialogNumberPanel(Context context) { 10 | super(context); 11 | } 12 | 13 | public DialogNumberPanel(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | } 16 | 17 | public DialogNumberPanel(Context context, AttributeSet attrs, int defStyle) { 18 | super(context, attrs, defStyle); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/view/DialpadKeyButton.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.nashlegend.quickid.view; 18 | 19 | import android.content.Context; 20 | import android.graphics.Rect; 21 | import android.os.Bundle; 22 | import android.util.AttributeSet; 23 | import android.view.MotionEvent; 24 | import android.view.View; 25 | import android.view.accessibility.AccessibilityEvent; 26 | import android.view.accessibility.AccessibilityManager; 27 | import android.view.accessibility.AccessibilityNodeInfo; 28 | import android.widget.FrameLayout; 29 | 30 | /** 31 | * Custom class for dialpad buttons. 32 | *

33 | * This class implements lift-to-type interaction when touch exploration is 34 | * enabled. 35 | */ 36 | public class DialpadKeyButton extends FrameLayout { 37 | /** Accessibility manager instance used to check touch exploration state. */ 38 | private AccessibilityManager mAccessibilityManager; 39 | 40 | /** Bounds used to filter HOVER_EXIT events. */ 41 | private Rect mHoverBounds = new Rect(); 42 | 43 | public interface OnPressedListener { 44 | public void onPressed(View view, boolean pressed); 45 | } 46 | 47 | private OnPressedListener mOnPressedListener; 48 | 49 | public void setOnPressedListener(OnPressedListener onPressedListener) { 50 | mOnPressedListener = onPressedListener; 51 | } 52 | 53 | public DialpadKeyButton(Context context, AttributeSet attrs) { 54 | super(context, attrs); 55 | initForAccessibility(context); 56 | } 57 | 58 | public DialpadKeyButton(Context context, AttributeSet attrs, int defStyle) { 59 | super(context, attrs, defStyle); 60 | initForAccessibility(context); 61 | } 62 | 63 | private void initForAccessibility(Context context) { 64 | mAccessibilityManager = (AccessibilityManager) context.getSystemService( 65 | Context.ACCESSIBILITY_SERVICE); 66 | } 67 | 68 | @Override 69 | public void setPressed(boolean pressed) { 70 | super.setPressed(pressed); 71 | if (mOnPressedListener != null) { 72 | mOnPressedListener.onPressed(this, pressed); 73 | } 74 | } 75 | 76 | @Override 77 | public void onSizeChanged(int w, int h, int oldw, int oldh) { 78 | super.onSizeChanged(w, h, oldw, oldh); 79 | 80 | mHoverBounds.left = getPaddingLeft(); 81 | mHoverBounds.right = w - getPaddingRight(); 82 | mHoverBounds.top = getPaddingTop(); 83 | mHoverBounds.bottom = h - getPaddingBottom(); 84 | } 85 | 86 | @Override 87 | public boolean performAccessibilityAction(int action, Bundle arguments) { 88 | if (action == AccessibilityNodeInfo.ACTION_CLICK) { 89 | simulateClickForAccessibility(); 90 | return true; 91 | } 92 | 93 | return super.performAccessibilityAction(action, arguments); 94 | } 95 | 96 | @Override 97 | public boolean onHoverEvent(MotionEvent event) { 98 | // When touch exploration is turned on, lifting a finger while inside 99 | // the button's hover target bounds should perform a click action. 100 | if (mAccessibilityManager.isEnabled() 101 | && mAccessibilityManager.isTouchExplorationEnabled()) { 102 | switch (event.getActionMasked()) { 103 | case MotionEvent.ACTION_HOVER_ENTER: 104 | // Lift-to-type temporarily disables double-tap activation. 105 | setClickable(false); 106 | break; 107 | case MotionEvent.ACTION_HOVER_EXIT: 108 | if (mHoverBounds.contains((int) event.getX(), (int) event.getY())) { 109 | simulateClickForAccessibility(); 110 | } 111 | setClickable(true); 112 | break; 113 | } 114 | } 115 | 116 | return super.onHoverEvent(event); 117 | } 118 | 119 | /** 120 | * When accessibility is on, simulate press and release to preserve the 121 | * semantic meaning of performClick(). Required for Braille support. 122 | */ 123 | private void simulateClickForAccessibility() { 124 | // Checking the press state prevents double activation. 125 | if (isPressed()) { 126 | return; 127 | } 128 | 129 | setPressed(true); 130 | 131 | // Stay consistent with performClick() by sending the event after 132 | // setting the pressed state but before performing the action. 133 | sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 134 | 135 | setPressed(false); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/view/DigitsEditText.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.nashlegend.quickid.view; 18 | 19 | import java.lang.reflect.Method; 20 | 21 | import android.annotation.SuppressLint; 22 | import android.content.Context; 23 | import android.graphics.Rect; 24 | import android.text.InputType; 25 | import android.util.AttributeSet; 26 | import android.view.MotionEvent; 27 | import android.view.inputmethod.InputMethodManager; 28 | import android.widget.EditText; 29 | 30 | /** 31 | * EditText which suppresses IME show up. 32 | */ 33 | public class DigitsEditText extends EditText { 34 | public DigitsEditText(Context context, AttributeSet attrs) { 35 | super(context, attrs); 36 | setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 37 | try { 38 | // 反射setShowSoftInputOnFocus(false); 39 | Method setShowSoftInputOnFocus; 40 | setShowSoftInputOnFocus = DigitsEditText.class.getMethod( 41 | "setShowSoftInputOnFocus", boolean.class); 42 | setShowSoftInputOnFocus.setAccessible(true); 43 | setShowSoftInputOnFocus.invoke(this, false); 44 | } catch (Exception e) { 45 | 46 | } 47 | } 48 | 49 | @Override 50 | protected void onFocusChanged(boolean focused, int direction, 51 | Rect previouslyFocusedRect) { 52 | super.onFocusChanged(focused, direction, previouslyFocusedRect); 53 | final InputMethodManager imm = ((InputMethodManager) getContext() 54 | .getSystemService(Context.INPUT_METHOD_SERVICE)); 55 | if (imm != null && imm.isActive(this)) { 56 | imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0); 57 | } 58 | } 59 | 60 | @SuppressLint("ClickableViewAccessibility") 61 | @Override 62 | public boolean onTouchEvent(MotionEvent event) { 63 | final boolean ret = super.onTouchEvent(event); 64 | // Must be done after super.onTouchEvent() 65 | final InputMethodManager imm = ((InputMethodManager) getContext() 66 | .getSystemService(Context.INPUT_METHOD_SERVICE)); 67 | if (imm != null && imm.isActive(this)) { 68 | imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0); 69 | } 70 | return ret; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/view/PhoneView.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.view; 3 | 4 | import net.nashlegend.quickid.model.Contact.PhoneStruct; 5 | import net.nashlegend.quickid.util.ContactHelper; 6 | 7 | import net.nashlegend.quickid.R; 8 | 9 | import android.annotation.SuppressLint; 10 | import android.content.Context; 11 | import android.graphics.Color; 12 | import android.os.Handler; 13 | import android.text.Spannable; 14 | import android.text.SpannableStringBuilder; 15 | import android.text.TextUtils; 16 | import android.text.style.ForegroundColorSpan; 17 | import android.view.LayoutInflater; 18 | import android.view.MotionEvent; 19 | import android.view.View; 20 | import android.widget.FrameLayout; 21 | import android.widget.ImageButton; 22 | import android.widget.LinearLayout; 23 | import android.widget.TextView; 24 | 25 | public class PhoneView extends FrameLayout { 26 | 27 | TextView numberText; 28 | LinearLayout layoutRoot; 29 | ImageButton smsButton; 30 | PhoneStruct phone; 31 | boolean matchNumber = false; 32 | String matchedNumber = ""; 33 | 34 | public PhoneView(Context context) { 35 | super(context); 36 | LayoutInflater inflater = (LayoutInflater) context 37 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 38 | inflater.inflate(R.layout.layout_phone_view, this); 39 | smsButton = (ImageButton) findViewById(R.id.button_send_sms); 40 | layoutRoot = (LinearLayout) findViewById(R.id.layout_phone); 41 | numberText = (TextView) findViewById(R.id.textview_phone_numbers); 42 | smsButton.setOnClickListener(onClickListener); 43 | layoutRoot.setClickable(true); 44 | layoutRoot.setOnTouchListener(new OnShortLongClickListener()); 45 | } 46 | 47 | public void setPhone(PhoneStruct p) { 48 | this.phone = p; 49 | numberText.setText(phone.phoneNumber); 50 | } 51 | 52 | public void setPhone(PhoneStruct p, String mStr) { 53 | this.phone = p; 54 | numberText.setText(phone.phoneNumber); 55 | String str = phone.phoneNumber; 56 | if (!TextUtils.isEmpty(mStr)) { 57 | int idx = str.indexOf(mStr); 58 | if (idx >= 0) { 59 | SpannableStringBuilder builder = new SpannableStringBuilder(str); 60 | ForegroundColorSpan redSpan = new ForegroundColorSpan(Color.RED); 61 | builder.setSpan(redSpan, idx, idx + mStr.length(), 62 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 63 | numberText.setText(builder); 64 | } 65 | } 66 | 67 | } 68 | 69 | private void onLongClick() { 70 | // do nothing 71 | } 72 | 73 | OnClickListener onClickListener = new OnClickListener() { 74 | 75 | @Override 76 | public void onClick(View v) { 77 | switch (v.getId()) { 78 | case R.id.button_send_sms: 79 | ContactHelper.sendSMS(numberText.getText().toString()); 80 | break; 81 | default: 82 | break; 83 | } 84 | } 85 | }; 86 | 87 | class OnShortLongClickListener implements OnTouchListener { 88 | long longDura = 1000L; 89 | long shortDura = 300L; 90 | long startTime = 0L; 91 | Handler handler = new Handler(); 92 | Runnable longPressRunnable = new Runnable() { 93 | public void run() { 94 | onLongClick(); 95 | } 96 | }; 97 | 98 | @SuppressLint("ClickableViewAccessibility") 99 | @Override 100 | public boolean onTouch(View v, MotionEvent event) { 101 | switch (event.getAction()) { 102 | case MotionEvent.ACTION_DOWN: 103 | startTime = System.currentTimeMillis(); 104 | handler.removeCallbacks(longPressRunnable); 105 | handler.postDelayed(longPressRunnable, longDura); 106 | break; 107 | case MotionEvent.ACTION_UP: 108 | handler.removeCallbacks(longPressRunnable); 109 | if (System.currentTimeMillis() - startTime < shortDura) { 110 | ContactHelper 111 | .makePhoneCall(numberText.getText().toString()); 112 | } 113 | break; 114 | case MotionEvent.ACTION_CANCEL: 115 | handler.removeCallbacks(longPressRunnable); 116 | break; 117 | default: 118 | break; 119 | } 120 | return false; 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/view/SideBar.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.view; 3 | 4 | import net.nashlegend.legendutils.Tools.DisplayUtil; 5 | import android.annotation.SuppressLint; 6 | import android.content.Context; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.Typeface; 11 | import android.util.AttributeSet; 12 | import android.view.MotionEvent; 13 | import android.view.View; 14 | 15 | public class SideBar extends View { 16 | 17 | OnTouchingLetterChangedListener onTouchingLetterChangedListener; 18 | String[] chars = { 19 | "#", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", 20 | "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" 21 | }; 22 | int choose = -1; 23 | Paint paint = new Paint(); 24 | boolean showBkg = false; 25 | 26 | public SideBar(Context context, AttributeSet attrs, int defStyle) { 27 | super(context, attrs, defStyle); 28 | } 29 | 30 | public SideBar(Context context, AttributeSet attrs) { 31 | super(context, attrs); 32 | } 33 | 34 | public SideBar(Context context) { 35 | super(context); 36 | } 37 | 38 | public void setChars(String[] cs) { 39 | chars = cs; 40 | } 41 | 42 | @Override 43 | protected void onDraw(Canvas canvas) { 44 | super.onDraw(canvas); 45 | if (chars.length > 0) { 46 | if (showBkg) { 47 | canvas.drawColor(Color.parseColor("#00000000")); 48 | } 49 | int height = getHeight(); 50 | int width = getWidth(); 51 | int singleHeight = height / chars.length; 52 | for (int i = 0; i < chars.length; i++) { 53 | paint.setColor(Color.BLACK); 54 | paint.setTextSize(DisplayUtil.dip2px(10, getContext())); 55 | paint.setTypeface(Typeface.DEFAULT_BOLD); 56 | paint.setAntiAlias(true); 57 | if (i == choose) { 58 | paint.setColor(Color.parseColor("#3399ff")); 59 | paint.setFakeBoldText(true); 60 | } 61 | float xPos = width / 2 - paint.measureText(chars[i]) / 2; 62 | float yPos = singleHeight * (i + 0.5f); 63 | canvas.drawText(chars[i], xPos, yPos, paint); 64 | paint.reset(); 65 | } 66 | } 67 | } 68 | 69 | @Override 70 | public boolean dispatchTouchEvent(MotionEvent event) { 71 | if (chars.length > 0) { 72 | final int action = event.getAction(); 73 | final float y = event.getY(); 74 | final int oldChoose = choose; 75 | final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener; 76 | final int c = (int) (y / getHeight() * chars.length); 77 | 78 | switch (action) { 79 | case MotionEvent.ACTION_DOWN: 80 | showBkg = true; 81 | if (oldChoose != c && listener != null) { 82 | if (c >= 0 && c < chars.length) { 83 | listener.onTouchingLetterChanged(chars[c]); 84 | choose = c; 85 | invalidate(); 86 | } 87 | } 88 | 89 | break; 90 | case MotionEvent.ACTION_MOVE: 91 | if (oldChoose != c && listener != null) { 92 | if (c >= 0 && c < chars.length) { 93 | listener.onTouchingLetterChanged(chars[c]); 94 | choose = c; 95 | invalidate(); 96 | } 97 | } 98 | break; 99 | case MotionEvent.ACTION_UP: 100 | showBkg = false; 101 | choose = -1; 102 | invalidate(); 103 | break; 104 | } 105 | } 106 | return true; 107 | } 108 | 109 | @SuppressLint("ClickableViewAccessibility") 110 | @Override 111 | public boolean onTouchEvent(MotionEvent event) { 112 | return super.onTouchEvent(event); 113 | } 114 | 115 | public void setOnTouchingLetterChangedListener( 116 | OnTouchingLetterChangedListener onTouchingLetterChangedListener) { 117 | this.onTouchingLetterChangedListener = onTouchingLetterChangedListener; 118 | } 119 | 120 | public interface OnTouchingLetterChangedListener { 121 | public void onTouchingLetterChanged(String s); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/net/nashlegend/quickid/view/SplitterView.java: -------------------------------------------------------------------------------- 1 | 2 | package net.nashlegend.quickid.view; 3 | 4 | import net.nashlegend.quickid.R; 5 | 6 | import android.content.Context; 7 | import android.util.AttributeSet; 8 | import android.view.LayoutInflater; 9 | import android.widget.FrameLayout; 10 | import android.widget.TextView; 11 | 12 | public class SplitterView extends FrameLayout { 13 | 14 | TextView textView; 15 | public String splitter; 16 | 17 | public SplitterView(Context context) { 18 | super(context); 19 | LayoutInflater inflater = (LayoutInflater) context 20 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 21 | inflater.inflate(R.layout.layout_splitter_view, this); 22 | textView = (TextView) findViewById(R.id.textview_splitter); 23 | } 24 | 25 | public SplitterView(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | } 28 | 29 | public SplitterView(Context context, AttributeSet attrs, int defStyle) { 30 | super(context, attrs, defStyle); 31 | } 32 | 33 | public void build(String txt) { 34 | splitter = txt; 35 | textView.setText(splitter); 36 | } 37 | 38 | } 39 | --------------------------------------------------------------------------------