一:[Contacts Data]在手机中预置联系人/Service Number
[DESCRIPTION]
预置联系人/Service Number
此方法比较适合预置联系人的数目不是特别多的情况
联系人比较多的情况,请参考:FAQ12935 预置联系人之Vcard预置联系人
[SOLUTION]
本方案实现预置联系人(包含姓名、号码信息)至手机中;并保证该联系人是只读的,无法被删除/编辑。
代码分为两部分:
Part One 将预置的联系人插入到数据库中;
Part Two 保证预置联系人只读,无法被编辑删
(在三个地方屏蔽对预置联系人进行编辑处理:联系人详情界面、联系人多选界面、新建联系人选择合并联系人时)。
【注意】
如果您不需要限制预置联系人的删除/编辑操作,加入Part One部分代码即可,并去掉第一步importDefaultReadonlyContact() 中的语句:contactvalues.put(RawContacts.IS_SDN_CONTACT, -2);
Part One
1.新建PresetContactsImportProcessor.java
Before Android N:
Path: alps\packages\apps\Contacts\src\com\mediatek\contacts\simservice
package com.mediatek.contacts.simservice;
import com.mediatek.contacts.simservice.SIMProcessorManager.ProcessorCompleteListener;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email; //for usim
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
import com.android.contacts.common.model.account.AccountType;
import android.os.RemoteException;
import java.util.ArrayList;
import com.mediatek.contacts.simservice.SIMProcessorManager.ProcessorCompleteListener;
import com.mediatek.contacts.simservice.SIMServiceUtils;
import com.mediatek.contacts.simservice.SIMServiceUtils.ServiceWorkData;
import com.mediatek.contacts.simcontact.SimCardUtils;
import com.mediatek.contacts.util.LogUtils;
import android.provider.ContactsContract.PhoneLookup;
public class PresetContactsImportProcessor extends SIMProcessorBase {
private static final String TAG = "PresetContactsImportProcessor";
private static boolean sIsRunningNumberCheck = false;
private static final int INSERT_PRESET_NUMBER_COUNT = xxx; //预置联系人的个数
private static final String INSERT_PRESET_NAME[] = {"xxx1","xxx2",...}; //各预置联系人的姓名
private static final String INSERT_PRESET_NUMBER[] = {"xxx1","xxx2",...}; //各预置联系人的号码
private int mSlotId;
private Context mContext;
public PresetContactsImportProcessor(Context context, int slotId, Intent intent, ProcessorCompleteListener listener) {
super(intent, listener);
mContext = context;
mSlotId = slotId;
}
@Override
public int getType() {
return SIMServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS;
}
@Override
public void doWork() {
if (isCancelled()) {
LogUtils.d(TAG, "[doWork]cancel import preset contacts work. Thread id=" + Thread.currentThread().getId());
return;
}
importDefaultReadonlyContact();
}
private void importDefaultReadonlyContact(){
Log.i(TAG, "isRunningNumberCheck before: " + sIsRunningNumberCheck);
if (sIsRunningNumberCheck) {
return;
}
sIsRunningNumberCheck = true;
for(int i = 0;i < INSERT_PRESET_NUMBER_COUNT; i++) {
Log.i(TAG, "isRunningNumberCheck after: " + sIsRunningNumberCheck);
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(INSERT_PRESET_NUMBER[i]));
Log.i(TAG, "getContactInfoByPhoneNumbers(), uri = " + uri);
Cursor contactCursor = mContext.getContentResolver().query(uri,
new String[] {PhoneLookup.DISPLAY_NAME, PhoneLookup.PHOTO_ID}, null, null, null);
try {
if (contactCursor != null && contactCursor.getCount() > 0) {
return;
} else {
final ArrayList operationList = new ArrayList();
ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
ContentValues contactvalues = new ContentValues();
contactvalues.put(RawContacts.ACCOUNT_NAME, AccountType.ACCOUNT_NAME_LOCAL_PHONE);
contactvalues.put(RawContacts.ACCOUNT_TYPE, AccountType.ACCOUNT_TYPE_LOCAL_PHONE);
contactvalues.put(RawContacts.INDICATE_PHONE_SIM, ContactsContract.RawContacts.INDICATE_PHONE);
contactvalues.put(RawContacts.IS_SDN_CONTACT, -2);
builder.withValues(contactvalues);
builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
operationList.add(builder.build());
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
builder.withValue(Phone.NUMBER, INSERT_PRESET_NUMBER[i]);
builder.withValue(Data.IS_PRIMARY, 1);
operationList.add(builder.build());
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
builder.withValue(StructuredName.DISPLAY_NAME, INSERT_PRESET_NAME[i]);
operationList.add(builder.build());
try {
mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
}
} finally {
// when this service start,but the contactsprovider has not been started yet.
// the contactCursor perhaps null, but not always.(first load will weekup the provider)
// so add null block to avoid nullpointerexception
if (contactCursor != null) {
contactCursor.close();
}
} //END for
Log.i(TAG, "isRunningNumberCheck insert: " + sIsRunningNumberCheck);
sIsRunningNumberCheck = false;
}
}
}
Android N:
Path: alps\packages\apps\Contacts\SimProcessor\src\com\mediatek\SimProcessor
package com.mediatek.simprocessor;
import android.content.Context;
import android.content.Intent;
import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.os.RemoteException;
import java.util.ArrayList;
import com.mediatek.simprocessor.SimProcessorManager.ProcessorCompleteListener;
import com.mediatek.simprocessor.SimServiceUtils;
import com.mediatek.simprocessor.Log;
import android.provider.ContactsContract.PhoneLookup;
public class PresetContactsImportProcessor extends SimProcessorBase {
private static final String TAG = "PresetContactsImportProcessor";
private static boolean sIsRunningNumberCheck = false;
private static final int INSERT_PRESET_NUMBER_COUNT = xxx; //预置联系人的个数
private static final String INSERT_PRESET_NAME[] = {"xxx1","xxx2", ...}; //各预置联系人的姓名
private static final String INSERT_PRESET_NUMBER[] = {"xxxx","xxxx", ...}; //各预置联系人的号码
private int mSubId;
private Context mContext;
public PresetContactsImportProcessor(Context context, int subId, Intent intent, ProcessorCompleteListener listener) {
super(intent, listener);
mContext = context;
mSubId = subId;
}
@Override
public int getType() {
return SimServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS;
}
@Override
public void doWork() {
if (isCancelled()) {
Log.d(TAG, "[doWork]cancel import preset contacts work. Thread id=" + Thread.currentThread().getId());
return;
}
importDefaultReadonlyContact();
}
private void importDefaultReadonlyContact(){
Log.i(TAG, "isRunningNumberCheck before: " + sIsRunningNumberCheck);
if (sIsRunningNumberCheck) {
return;
}
sIsRunningNumberCheck = true;
for(int i = 0;i < INSERT_PRESET_NUMBER_COUNT; i++) {
Log.i(TAG, "isRunningNumberCheck after: " + sIsRunningNumberCheck);
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(INSERT_PRESET_NUMBER[i]));
Log.i(TAG, "getContactInfoByPhoneNumbers(), uri = " + uri);
Cursor contactCursor = mContext.getContentResolver().query(uri,
new String[] {PhoneLookup.DISPLAY_NAME, PhoneLookup.PHOTO_ID}, null, null, null);
try {
if (contactCursor != null && contactCursor.getCount() > 0) {
return;
} else {
final ArrayList operationList = new ArrayList();
ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
ContentValues contactvalues = new ContentValues();
contactvalues.put(RawContacts.ACCOUNT_NAME, "Phone");
contactvalues.put(RawContacts.ACCOUNT_TYPE, "Local Phone Account");
contactvalues.put(RawContacts.INDICATE_PHONE_SIM, ContactsContract.RawContacts.INDICATE_PHONE);
contactvalues.put(RawContacts.IS_SDN_CONTACT, -2);
builder.withValues(contactvalues);
builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
operationList.add(builder.build());
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
builder.withValue(Phone.NUMBER, INSERT_PRESET_NUMBER[i]);
builder.withValue(Data.IS_PRIMARY, 1);
operationList.add(builder.build());
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
builder.withValue(StructuredName.DISPLAY_NAME, INSERT_PRESET_NAME[i]);
operationList.add(builder.build());
try {
mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
}
} finally {
// when this service start,but the contactsprovider has not been started yet.
// the contactCursor perhaps null, but not always.(first load will weekup the provider)
// so add null block to avoid nullpointerexception
if (contactCursor != null) {
contactCursor.close();
}
}
Log.i(TAG, "isRunningNumberCheck insert: " + sIsRunningNumberCheck);
sIsRunningNumberCheck = false;
}
}
}
2.
Before Android N:
修改SIMServiceUtils.java
Path: alps\packages\apps\ContactsCommon\src\com\mediatek\contacts\simservice
Android N:
修改SimServiceUtils.java
Path: alps\packages\apps\Contacts\SimProcessor\src\com\mediatek\SimProcessor
添加
public static final int SERVICE_WORK_IMPORT_PRESET_CONTACTS = 5;
3.
Before Android N:
修改SIMProcessorManager.java
Path: alps\packages\apps\Contacts\src\com\mediatek\contacts\simservice
在createProcessor函数里添加
else if (workType == SIMServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS) {
processor = new PresetContactsImportProcessor(context, subId, intent, listener);
}
Android N:
修改SimProcessorManager.java
Path: alps\packages\apps\Contacts\SimProcessor\src\com\mediatek\SimProcessor
在createProcessor函数里添加
else if (workType == SimServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS) {
processor = new PresetContactsImportProcessor(context, subId, intent, listener);
}
4.修改BootCmpReceiver.java
Before Android N:
Path: alps\packages\apps\Contacts\src\com\mediatek\contacts\simservice
添加如下方法:
/**
* when boot complete,preset the service number directly.
*/
private void presetServiceNumber(Context context) {
Log.d(TAG, "presetServiceNumber");
startSimService(context, -1, SIMServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS);
}
Android N:
Path: alps\packages\apps\Contacts\SimProcessor\src\com\mediatek\SimProcessor
添加如下方法:
/**
* when boot complete,preset the service number directly.
*/
private void presetServiceNumber(Context context) {
Log.d(TAG, "presetServiceNumber");
startSimService(context, -1, SimServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS);
}
5. 修改BootCmpReceiver.java
Before Android N:
Path: alps\packages\apps\Contacts\src\com\mediatek\contacts\simservice
在onReceive()方法
public void onReceive(Context context, Intent intent) {
... ...
} else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
// fix ALPS01003520,when boot complete,remove the contacts if the
// card of a slot has been removed
if (!isPhbReady()) {
processBootComplete(context);
}
}
... ...
}
修改为:
public void onReceive(Context context, Intent intent) {
... ...
} else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
// fix ALPS01003520,when boot complete,remove the contacts if the
// card of a slot has been removed
if (!isPhbReady()) {
processBootComplete(context);
}
// [START] add for Preset service number
presetServiceNumber(context);
// [END]
}
... ...
}
Android N:
Path: alps\packages\apps\Contacts\SimProcessor\src\com\mediatek\SimProcessor
在onReceive()方法
public void onReceive(Context context, Intent intent) {
... ...
} else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
if (!isPhbReady()) {
processBootComplete(context);
} else {
processDupSimContacts(context);
}
}
... ...
}
修改为:
public void onReceive(Context context, Intent intent) {
... ...
} else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
if (!isPhbReady()) {
processBootComplete(context);
} else {
processDupSimContacts(context);
}
// [START] add for Preset service number
presetServiceNumber(context);
// [END]
}
... ...
}
Part Two
Before Android N:
File:DefaultContactListAdapter.java
Path: alps\packages\apps\ContactsCommon\src\com\android\contacts\common\list
Android N:
File:ContactsCommonListUtils.java
Path: alps\packages\apps\ContactsCommon\src\com\mediatek\contacts\util
configureOnlyShowPhoneContactsSelection函数中如下语句:
selection.append(Contacts.INDICATE_PHONE_SIM + "= ?");
selectionArgs.add("-1");
之后增加下面的代码
selection.append(" AND " + RawContacts.IS_SDN_CONTACT + " > -2");
- File:Contact.java
- alps\packages\apps\ContactsCommon\src\com\android\contacts\common\model
增加如下函数:
//add for Preset Contacts
public boolean isReadOnlyContact() {
return mIsSdnContact == -2;
}
- Android N:
- File:ContactLoaderFragment.java
Path:alps\packages\apps\contacts\src\com\android\contacts\detail
- 将isContactEditable函数修改为:
public boolean isContactEditable() {
// return mContactData != null && !mContactData.isDirectoryEntry() && !mContactData.isSdnContacts() && !mContactData.isInternationalDialNumber(); // before
return mContactData != null && !mContactData.isDirectoryEntry() && !mContactData.isSdnContacts() && !mContactData.isInternationalDialNumber() && !mContactData.isReadOnlyContact() ; // after
}
Android N:
File:QuickContactActivity.java
Path:alps\packages\apps\contacts\src\com\android\contacts\quickcontact
将isContactEditable函数修改为:
private boolean isContactEditable() {
// return mContactData != null && !mContactData.isDirectoryEntry() && !mContactData.isSdnContacts() ; // before
return mContactData != null && !mContactData.isDirectoryEntry() && !mContactData.isSdnContacts() && !mContactData.isReadOnlyContact() ; // after
}
4.
File:ContactEntryListAdapter.java
Path:alps\packages\apps\contactscommon\src\com\android\contacts\common\list
在文件最后增加以下代码:
public boolean showReadOnlyContact = true;
public void setShowReadOnlyContact(boolean canDelete) {
showReadOnlyContact = canDelete;
}
5.
File:ContactEntryListFragment.java
- Path:alps\packages\apps\contactscommon\src\com\android\contacts\common\list
添加代码:
protected boolean isInstanceOfContactsMultiDeletionFragment(){
return false;
}
在onCreateLoader函数中,倒数第二句
mAdapter.configureLoader(loader, directoryId);
之前增加语句:
mAdapter.setShowReadOnlyContact(isInstanceOfContactsMultiDeletionFragment() ? false : true);
mAdapter.configureLoader(loader, directoryId);
- Android M:
- File: ContactsMultiDeletionFragment.java
Path:alps\packages\apps\Contacts\src\com\mediatek\contacts\list
添加代码:
protected boolean isInstanceOfContactsMultiDeletionFragment(){
return true;
}
[ Android M:
C File: packages/apps/ContactsCommon/src/com/android/contacts/common/list/ContactEntryListAdapter.java
public boolean isSdnNumber(int position) {
boolean isSdnNumber = false;
int partition = getPartitionForPosition(position);
if (partition >= 0) {
// Save the old cursor position - the call to getItem() may modify the cursor
// position.
int offset = getCursor(partition).getPosition();
Cursor cursor = (Cursor) getItem(position);
if (cursor != null) {
// [START]
// Before
// isSdnNumber = cursor.getInt(ContactQuery.IS_SDN_CONTACT) == 1;
// After
final long isSdn = cursor.getInt(ContactQuery.IS_SDN_CONTACT);
if (isSdn == 1 || isSdn == -2) {
isSdnNumber = true;
}
Log.d(TAG, "[isSdnNumber] isSdn = " + isSdn + ",isSdnNumber = " + isSdnNumber); //add log
// [END]
// Restore the old cursor position.
cursor.moveToPosition(offset);
}
}
return isSdnNumber;
}
Android N:
C File: packages/apps/ContactsCommon/src/com/android/contacts/common/list/ContactEntryListAdapter.java
public boolean isSdnNumber(int position) {
boolean isSdnNumber = false;
int partition = getPartitionForPosition(position);
if (partition >= 0) {
// Save the old cursor position - the call to getItem() may modify the cursor
// position.
int offset = getCursor(partition).getPosition();
Cursor cursor = (Cursor) getItem(position);
if (cursor != null) {
// [START]
// Before
// isSdnNumber = cursor.getInt(cursor.getColumnIndex(Contacts.IS_SDN_CONTACT)) == 1;
// After
final long isSdn = cursor.getInt(cursor.getColumnIndex(Contacts.IS_SDN_CONTACT));
if (isSdn == 1 || isSdn == -2) {
isSdnNumber = true;
}
Log.d(TAG, "[isSdnNumber] isSdn = " + isSdn + ",isSdnNumber = " + isSdnNumber); //add log
// [END]
// Restore the old cursor position.
cursor.moveToPosition(offset);
}
}
return isSdnNumber;
}
7.
Before Android M:
File:MultiContactsBasePickerAdapter.java
Path:alps\packages\apps\contacts\src\com\mediatek\contacts\list
After Android M:
File:MultiBasePickerAdapter.java
Path:alps\packages\apps\contacts\src\com\mediatek\contacts\list
在configureSelection函数最后的语句
loader.setSelection(selection.toString());
之前增加语句:
if (!showReadOnlyContact ) {
selection.append(" AND " + Contacts.IS_SDN_CONTACT + "=0");
}
loader.setSelection(selection.toString());
8.File:AggregationSuggestionEngine.java
Path:alps\packages\apps\contacts\src\com\android\contacts\editor
在loadAggregationSuggestions函数
在语句:
sb.append(" AND " + Contacts.INDICATE_PHONE_SIM + "=-1");
之后添加:
sb.append(" AND " + Contacts.IS_SDN_CONTACT + "!=-2");
9.
File:JoinContactListAdapter.java
Path:packages\apps\contacts\src\com\android\contacts\list
函数:public void configureLoader(CursorLoader cursorLoader, long directoryId)
Before Android N:
将:
loader.setSelection(Contacts._ID + "!=?"+" AND " + Contacts.INDICATE_PHONE_SIM + "=-1");
修改为:
loader.setSelection(Contacts._ID + "!=?"+" AND " + Contacts.INDICATE_PHONE_SIM + "=-1" + " AND " + Contacts.IS_SDN_CONTACT + "!=-2");
Android N:
将:
if (ContactsPortableUtils.MTK_PHONE_BOOK_SUPPORT) {
loader.setSelection(Contacts._ID + "!=?" + " AND " + Contacts.INDICATE_PHONE_SIM
+ "=-1");
} else {
loader.setSelection(Contacts._ID + "!=?");
}
修改为:
if (ContactsPortableUtils.MTK_PHONE_BOOK_SUPPORT) {
loader.setSelection(Contacts._ID + "!=?" + " AND " + Contacts.INDICATE_PHONE_SIM
+ "=-1" + " AND " + Contacts.IS_SDN_CONTACT + "!=-2");
} else {
loader.setSelection(Contacts._ID + "!=?" + " AND " + Contacts.IS_SDN_CONTACT + "!=-2");
}
10.
Android N:
File: MultiSelectEntryContactListAdapter
Path: packages\apps\contacts\src\com\android\contacts\list
语句:
if (ContactsPortableUtils.MTK_PHONE_BOOK_SUPPORT
&& cursor.getInt(
cursor.getColumnIndex(ContactsContract.Contacts.IS_SDN_CONTACT)) == 1) {
view.setClickable(false);
view.hideCheckBox();
return;
}
修改为:
if ((ContactsPortableUtils.MTK_PHONE_BOOK_SUPPORT
&& cursor.getInt(cursor.getColumnIndex(
ContactsContract.Contacts.IS_SDN_CONTACT)) == 1)
|| cursor.getInt(cursor.getColumnIndex(
ContactsContract.Contacts.IS_SDN_CONTACT)) == -2) {
view.setClickable(false);
view.hideCheckBox();
return;
}
//End for Test
二:【Contacts Data】预置联系人之Vcard预置联系人
[DESCRIPTION]
通过Vcard文件方式预置联系人,支持几乎所有联系人信息种类,包括联系人Name、Phone、Email、Avatar、Postal、Note、Website、Organization等。
[SOLUTION]
Part1:准备预置联系人VCard文件
1. 在手机编辑需要预置的联系人,以VCard方式导出,重命名为preset_contacts.vcf
2. 将preset_contacts.vcf文件放置到路径:alps\packages\apps\Contacts\assets
注意:默认从手机中导出的Vcard文件,若联系人有头像,其清晰度会降低。若需要保证联系人头像的清晰度,需要使用工具JPEG2VcardAvatar,下载链接:http://online.mediatek.inc/_layouts/15/mol/tool/ext/TOLProps.aspx?toolid=1a6482b6-3e3b-4f4f-bafa-67f50d827160
因JB与KK上Contacts结构不相同,以下部分分两个软件版本
KK / L / M 版本
Part2:导入预置联系人VCard文件
1. 修改SIMServiceUtils.java
Path:alps\packages\apps\ContactsCommon\src\com\mediatek\contacts\simservice
添加
public static final int SERVICE_WORK_IMPORT_PRESET_CONTACTS = 5;
2. 修改SIMProcessorManager.java
Path:alps\packages\apps\Contacts\src\com\mediatek\contacts\simservice
在SIMProcessorManager.java中createProcessor函数里添加
else if (workType == SIMServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS) {
processor = new ImportPresetContactProcessor(context, ImportPresetContactProcessor.PRESET_CONTACT_VCARD, 0); }
4. 修改BootCmpReceiver.java
Path:alps\packages\apps\Contacts\src\com\mediatek\contacts\simcontact
在BootCmpReceiver.java中processBootComplete()方法最后添加代码
startSimService(-1, SIMServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS);
5. 新建ImportPresetContactProcessor.java
Path: alps\packages\apps\Contacts\src\com\mediatek\contacts\vcard
package com.mediatek.contacts.vcard;
import android.content.Context;
import android.accounts.Account;
import android.content.ContentResolver;
import android.net.Uri;
// The following lines are provided and maintained by Mediatek Inc.
import android.os.PowerManager;
import android.os.Process;
// The previous lines are provided and maintained by Mediatek Inc.
import android.util.Log;
import com.mediatek.contacts.ext.ContactPluginDefault;
import com.android.vcard.VCardEntryCounter;
import com.android.vcard.VCardSourceDetector;
import com.android.vcard.VCardEntry;
import com.android.vcard.VCardEntryCommitter;
import com.android.vcard.VCardEntryConstructor;
import com.android.vcard.VCardEntryHandler;
import com.android.vcard.VCardInterpreter;
import com.android.vcard.VCardParser;
import com.android.vcard.VCardParser_V21;
import com.android.vcard.VCardParser_V30;
import com.android.vcard.exception.VCardException;
import com.android.vcard.exception.VCardNestedException;
import com.android.vcard.exception.VCardNotSupportedException;
import com.android.vcard.exception.VCardVersionException;
import com.mediatek.contacts.ExtensionManager;
import com.android.contacts.common.vcard.ImportRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
//add by MTK for importing preset contact START
import com.mediatek.contacts.simservice.SIMServiceUtils;
import com.mediatek.contacts.simservice.SIMProcessorBase;
//add by MTK for importing preset contact END
/**
* Class for processing one import request from a user. Dropped after importing requested Uri(s).
* {@link VCardService} will create another object when there is another import request.
*/
public class ImportPresetContactProcessor extends SIMProcessorBase implements VCardEntryHandler {
/* package */ static final String VCARD_URI_ARRAY = "vcard_uri";
/* package */ static final String ESTIMATED_VCARD_TYPE_ARRAY = "estimated_vcard_type";
/* package */ static final String ESTIMATED_CHARSET_ARRAY = "estimated_charset";
/* package */ static final String VCARD_VERSION_ARRAY = "vcard_version";
/* package */ static final String ENTRY_COUNT_ARRAY = "entry_count";
/* package */ final static int VCARD_VERSION_AUTO_DETECT = 0;
/* package */ final static int VCARD_VERSION_V21 = 1;
/* package */ final static int VCARD_VERSION_V30 = 2;
private static final String LOG_TAG = "VCardImportPresetContact";
public static final String PRESET_CONTACT_VCARD =
"file:///android_asset/preset_contacts.vcf";
private static final boolean DEBUG = false;
private final Context mContext;
private final ContentResolver mResolver;
private final Uri mUri;
private final String mDisplayName;
private final int mJobId;
// TODO: remove and show appropriate message instead.
private final List<Uri> mFailedUris = new ArrayList<Uri>();
private VCardParser mVCardParser;
private volatile boolean mCanceled;
private volatile boolean mDone;
/*
* Change Feature by Mediatek Begin.
* Descriptions: handle cancel in the cancel function
*/
private volatile boolean mIsRunning = false;
/*
* Change Feature by Mediatek End.
*/
private int mCurrentCount = 0;
private int mTotalCount = 0;
/*
* New Feature by Mediatek Begin.
* Descriptions: All of process should not be stopped by PM.
*/
private PowerManager.WakeLock mWakeLock;
/*
* New Feature by Mediatek End.
*/
public ImportPresetContactProcessor(final Context context,
final String vcardFilePath, final int jobId) {
super(null, null);
mContext = context;
mResolver = mContext.getContentResolver();
mJobId = jobId;
mUri = Uri.parse(vcardFilePath);
mDisplayName = mUri.getLastPathSegment();
Log.d(LOG_TAG, "vCard Uri = " + mUri.getPath() + ", vCard DisplayName = " + mDisplayName);
/*
* New Feature by Mediatek Begin.
* Descriptions: All of process should not be stopped by PM.
*/
final PowerManager powerManager = (PowerManager) mContext.getApplicationContext()
.getSystemService("power");
mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK
| PowerManager.ON_AFTER_RELEASE, LOG_TAG);
/*
* New Feature by Mediatek End.
*/
Log.d(LOG_TAG, "ImportProcessor() ");
}
@Override
public void onStart() {
// do nothing
}
@Override
public void onEnd() {
Log.d(LOG_TAG, "onEnd() ");
// do nothing
}
@Override
public void onEntryCreated(VCardEntry entry) {
mCurrentCount++;
}
@Override
public final int getType() {
//add by MTK for importing preset contact START
//return VCardService.TYPE_IMPORT;
return SIMServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS;
//add by MTK for importing preset contact END
}
//add by MTK for importing preset contact START
public void doWork() {
Log.d(LOG_TAG,"ImportPresetContactProcessor, run()");
//add by MTK for importing preset contact END
/*
* Change Feature by Mediatek Begin.
* Descriptions: handle cancel in the cancel function
*/
mIsRunning = true;
/*
* Change Feature by Mediatek End.
*/
/*
* New Feature by Mediatek Begin.
* Descriptions: All of process should not be stopped by PM.
*/
mWakeLock.acquire();
/*
* New Feature by Mediatek End.
*/
// ExecutorService ignores RuntimeException, so we need to show it here.
try {
/*
* Bug Fix by Mediatek Begin.
* CR ID: ALPS00115856
* Descriptions: Set the thread priority to lowest
* and give up the change for Query
*/
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
/*
* Bug Fix by Mediatek End.
*/
runInternal();
} catch (OutOfMemoryError e) {
Log.e(LOG_TAG, "OutOfMemoryError thrown during import", e);
throw e;
} catch (RuntimeException e) {
Log.e(LOG_TAG, "RuntimeException thrown during import", e);
throw e;
} finally {
synchronized (this) {
mDone = true;
}
/*
* New Feature by Mediatek Begin.
* Descriptions: All of process should not be stopped by PM.
*/
if (mWakeLock != null && mWakeLock.isHeld()) {
mWakeLock.release();
}
/*
* New Feature by Mediatek End.
*/
}
}
private void runInternal() {
int count = context.getContentResolver().delete( RawContacts.CONTENT_URI.buildUpon().build(), RawContacts.IS_SDN_CONTACT + " = -2", null);
LogUtils.d(TAG,"[deletePresetContacts]count:" + count);
Log.i(LOG_TAG, String.format("vCard import (id: %d) has started.", mJobId));
final ImportRequest request = getImportRequest(mUri, mDisplayName);
if (isCancelled()) {
Log.i(LOG_TAG, "Canceled before actually handling parameter (" + request.uri + ")");
return;
}
final int[] possibleVCardVersions;
if (request.vcardVersion == VCARD_VERSION_AUTO_DETECT) {
/**
* Note: this code assumes that a given Uri is able to be opened more than once,
* which may not be true in certain conditions.
*/
possibleVCardVersions = new int[] {
VCARD_VERSION_V21,
VCARD_VERSION_V30
};
} else {
possibleVCardVersions = new int[] {
request.vcardVersion
};
}
final Uri uri = request.uri;
final Account account = request.account;
final int estimatedVCardType = request.estimatedVCardType;
final String estimatedCharset = request.estimatedCharset;
final int entryCount = request.entryCount;
mTotalCount += entryCount;
/**
* add for import/export group infomations feature . start
* original code:
* final VCardEntryConstructor constructor =
* new VCardEntryConstructor(estimatedVCardType, account, estimatedCharset);
*/
final VCardEntryConstructor constructor = new VCardEntryConstructorForPresetContact(
estimatedVCardType, account, estimatedCharset);
/**
* add for import/export group infomations feature . end
*/
final VCardEntryCommitter committer = new VCardEntryCommitter(mResolver);
constructor.addEntryHandler(committer);
constructor.addEntryHandler(this);
InputStream is = null;
boolean successful = false;
try {
if (uri != null) {
Log.i(LOG_TAG, "start importing one vCard (Uri: " + uri + ")");
is = mContext.getAssets().open(uri.getLastPathSegment());
//is = mResolver.openInputStream(uri);
} else if (request.data != null){
Log.i(LOG_TAG, "start importing one vCard (byte[])");
is = new ByteArrayInputStream(request.data);
}
if (is != null) {
successful = readOneVCard(is, estimatedVCardType, estimatedCharset, constructor,
possibleVCardVersions);
}
} catch (IOException e) {
successful = false;
} finally {
if (is != null) {
try {
is.close();
} catch (Exception e) {
// ignore
}
}
}
if (successful) {
// TODO: successful becomes true even when cancelled. Should return more appropriate
// value
if (isCancelled()) {
Log.i(LOG_TAG, "vCard import has been canceled (uri: " + uri + ")");
// Cancel notification will be done outside this method.
} else {
Log.i(LOG_TAG, "Successfully finished importing one vCard file: " + uri);
List<Uri> uris = committer.getCreatedUris();
}
} else {
Log.w(LOG_TAG, "Failed to read one vCard file: " + uri);
mFailedUris.add(uri);
}
}
private boolean readOneVCard(InputStream is, int vcardType, String charset,
final VCardInterpreter interpreter,
final int[] possibleVCardVersions) {
boolean successful = false;
final int length = possibleVCardVersions.length;
for (int i = 0; i < length; i++) {
final int vcardVersion = possibleVCardVersions[i];
try {
if (i > 0 && (interpreter instanceof VCardEntryConstructor)) {
// Let the object clean up internal temporary objects,
((VCardEntryConstructor) interpreter).clear();
}
// We need synchronized block here,
// since we need to handle mCanceled and mVCardParser at once.
// In the worst case, a user may call cancel() just before creating
// mVCardParser.
synchronized (this) {
mVCardParser = (vcardVersion == VCARD_VERSION_V30 ?
new VCardParser_V30(vcardType) :
new VCardParser_V21(vcardType));
if (isCancelled()) {
Log.i(LOG_TAG, "ImportProcessor already recieves cancel request, so " +
"send cancel request to vCard parser too.");
mVCardParser.cancel();
}
}
mVCardParser.parse(is, interpreter);
successful = true;
break;
} catch (IOException e) {
Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
} catch (VCardNestedException e) {
// This exception should not be thrown here. We should instead handle it
// in the preprocessing session in ImportVCardActivity, as we don't try
// to detect the type of given vCard here.
//
// TODO: Handle this case appropriately, which should mean we have to have
// code trying to auto-detect the type of given vCard twice (both in
// ImportVCardActivity and ImportVCardService).
Log.e(LOG_TAG, "Nested Exception is found.");
} catch (VCardNotSupportedException e) {
Log.e(LOG_TAG, e.toString());
} catch (VCardVersionException e) {
if (i == length - 1) {
Log.e(LOG_TAG, "Appropriate version for this vCard is not found.");
} else {
// We'll try the other (v30) version.
}
} catch (VCardException e) {
Log.e(LOG_TAG, e.toString());
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
return successful;
}
@Override
public synchronized boolean cancel(boolean mayInterruptIfRunning) {
if (DEBUG) Log.d(LOG_TAG, "ImportProcessor received cancel request");
if (mDone || mCanceled) {
return false;
}
mCanceled = true;
synchronized (this) {
if (mVCardParser != null) {
mVCardParser.cancel();
}
}
return true;
}
@Override
public synchronized boolean isCancelled() {
return mCanceled;
}
@Override
public synchronized boolean isDone() {
return mDone;
}
private ImportRequest getImportRequest(final Uri localDataUri, final String displayName){
final ImportRequest request;
try {
request = constructImportRequest(null, localDataUri, displayName);
} catch (VCardException e) {
Log.e(LOG_TAG, "Maybe the file is in wrong format", e);
///fixed cr ALPS00598462
return null;
/*
* Bug Fix by Mediatek Begin. CR ID: ALPS00318987
*/
} catch (IllegalArgumentException e) {
Log.e(LOG_TAG, "Maybe the file is in wrong format", e);
///fixed cr ALPS00598462
return null;
/*
* Bug Fix by Mediatek End.
*/
} catch (IOException e) {
Log.e(LOG_TAG, "Unexpected IOException", e);
///fixed cr ALPS00598462
return null;
}
return request;
}
private ImportRequest constructImportRequest(final byte[] data,
final Uri localDataUri, final String displayName)
throws IOException, VCardException {
final ContentResolver resolver = mContext.getApplicationContext().getContentResolver();
VCardEntryCounter counter = null;
VCardSourceDetector detector = null;
int vcardVersion = VCARD_VERSION_V21;
try {
boolean shouldUseV30 = false;
InputStream is;
if (data != null) {
is = new ByteArrayInputStream(data);
} else {
is = mContext.getAssets().open(localDataUri.getLastPathSegment());
//is = resolver.openInputStream(localDataUri);
}
mVCardParser = new VCardParser_V21();
try {
counter = new VCardEntryCounter();
detector = new VCardSourceDetector();
mVCardParser.addInterpreter(counter);
mVCardParser.addInterpreter(detector);
mVCardParser.parse(is);
} catch (VCardVersionException e1) {
try {
is.close();
} catch (IOException e) {
}
shouldUseV30 = true;
if (data != null) {
is = new ByteArrayInputStream(data);
} else {
is = mContext.getAssets().open(localDataUri.getLastPathSegment());
//is = resolver.openInputStream(localDataUri);
}
mVCardParser = new VCardParser_V30();
try {
counter = new VCardEntryCounter();
detector = new VCardSourceDetector();
mVCardParser.addInterpreter(counter);
mVCardParser.addInterpreter(detector);
mVCardParser.parse(is);
} catch (VCardVersionException e2) {
throw new VCardException("vCard with unspported version.");
}
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
vcardVersion = shouldUseV30 ? VCARD_VERSION_V30 : VCARD_VERSION_V21;
} catch (VCardNestedException e) {
Log.w(LOG_TAG, "Nested Exception is found (it may be false-positive).");
// Go through without throwing the Exception, as we may be able to detect the
// version before it
}
return new ImportRequest(null,
data, localDataUri, displayName,
detector.getEstimatedType(),
detector.getEstimatedCharset(),
vcardVersion, counter.getCount());
}
}
6. 新建VCardEntryConstructorForPresetContact.java
path: alps\packages\apps\ContactsCommon\src\com\mediatek\contacts\vcard
package com.mediatek.contacts.vcard;
import android.accounts.Account;
import com.android.vcard.VCardConfig;
import com.android.vcard.VCardEntryConstructor;
import com.android.vcard.VCardEntry;
public class VCardEntryConstructorForPresetContact extends VCardEntryConstructor {
private final int mVCardType;
private final Account mAccount;
public VCardEntryConstructorForPresetContact() {
this(VCardConfig.VCARD_TYPE_V21_GENERIC, null, null);
}
public VCardEntryConstructorForPresetContact(final int vcardType) {
this(vcardType, null, null);
}
public VCardEntryConstructorForPresetContact(final int vcardType, final Account account) {
this(vcardType, account, null);
}
/**
* @deprecated targetCharset is not used anymore.
* Use {@link #VCardEntryConstructor(int, Account)}
*/
@Deprecated
public VCardEntryConstructorForPresetContact(final int vcardType, final Account account,
String targetCharset) {
mVCardType = vcardType;
mAccount = account;
}
protected VCardEntry getVCardEntry() {
return new VCardEntryForPresetContact(mVCardType, mAccount);
}
}
7. 新建VCardEntryForPresetContact.java
path: alps\packages\apps\ContactsCommon\src\com\mediatek\contacts\vcard
package com.mediatek.contacts.vcard;
import com.android.vcard.VCardEntry;
import android.accounts.Account;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.provider.ContactsContract.RawContacts;
import java.util.ArrayList;
import java.util.List;
import com.android.contacts.common.model.account.AccountType;
public class VCardEntryForPresetContact extends VCardEntry{
public VCardEntryForPresetContact(int vcardType, Account account){
super(vcardType, account);
}
@Override
public ArrayList<ContentProviderOperation> constructInsertOperations(ContentResolver resolver,
ArrayList<ContentProviderOperation> operationList) {
if (operationList == null) {
operationList = new ArrayList<ContentProviderOperation>();
}
if (isIgnorable()) {
return operationList;
}
final int backReferenceIndex = operationList.size();
// After applying the batch the first result's Uri is returned so it is important that
// the RawContact is the first operation that gets inserted into the list.
ContentProviderOperation.Builder builder = ContentProviderOperation
.newInsert(RawContacts.CONTENT_URI);
builder.withValue(RawContacts.ACCOUNT_NAME, AccountType.ACCOUNT_NAME_LOCAL_PHONE);
builder.withValue(RawContacts.ACCOUNT_TYPE, AccountType.ACCOUNT_TYPE_LOCAL_PHONE);
builder.withValue(RawContacts.INDICATE_PHONE_SIM, RawContacts.INDICATE_PHONE);
builder.withValue(RawContacts.IS_SDN_CONTACT, -2);
operationList.add(builder.build());
int start = operationList.size();
iterateAllData(new InsertOperationConstrutor(operationList, backReferenceIndex));
int end = operationList.size();
return operationList;
}
private class InsertOperationConstrutor implements EntryElementIterator {
private final List<ContentProviderOperation> mOperationList;
private final int mBackReferenceIndex;
public InsertOperationConstrutor(List<ContentProviderOperation> operationList,
int backReferenceIndex) {
mOperationList = operationList;
mBackReferenceIndex = backReferenceIndex;
}
@Override
public void onIterationStarted() {
}
@Override
public void onIterationEnded() {
}
@Override
public void onElementGroupStarted(EntryLabel label) {
}
@Override
public void onElementGroupEnded() {
}
@Override
public boolean onElement(EntryElement elem) {
if (!elem.isEmpty()) {
elem.constructInsertOperation(mOperationList, mBackReferenceIndex);
}
return true;
}
}
}
displayName = sourceUri.getLastPathSegment();
private ImportRequest constructImportRequest(final byte[] data,
final Uri localDataUri, final String displayName)
throws IOException, VCardException {
final ContentResolver resolver = ImportVCardActivity.this.getContentResolver();
VCardEntryCounter counter = null;
VCardSourceDetector detector = null;
int vcardVersion = VCARD_VERSION_V21;
try {
boolean shouldUseV30 = false;
InputStream is;
if (data != null) {
is = new ByteArrayInputStream(data);
} else {
is = resolver.openInputStream(localDataUri);
}
mVCardParser = new VCardParser_V21();
try {
counter = new VCardEntryCounter();
detector = new VCardSourceDetector();
mVCardParser.addInterpreter(counter);
mVCardParser.addInterpreter(detector);
mVCardParser.parse(is);
} catch (VCardVersionException e1) {
try {
is.close();
} catch (IOException e) {
}
shouldUseV30 = true;
if (data != null) {
is = new ByteArrayInputStream(data);
} else {
is = resolver.openInputStream(localDataUri);
}
mVCardParser = new VCardParser_V30();
try {
counter = new VCardEntryCounter();
detector = new VCardSourceDetector();
mVCardParser.addInterpreter(counter);
mVCardParser.addInterpreter(detector);
mVCardParser.parse(is);
} catch (VCardVersionException e2) {
throw new VCardException("vCard with unspported version.");
}
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
vcardVersion = shouldUseV30 ? VCARD_VERSION_V30 : VCARD_VERSION_V21;
} catch (VCardNestedException e) {
Log.w(LOG_TAG, "Nested Exception is found (it may be false-positive).");
// Go through without throwing the Exception, as we may be able to detect the
// version before it
}
return new ImportRequest(mAccount,
data, localDataUri, displayName,
detector.getEstimatedType(),
detector.getEstimatedCharset(),
vcardVersion, counter.getCount());
}
Part3:保证预置联系人不可编辑
1. File:DefaultContactListAdapter.java
Path: alps\packages\apps\ContactsCommon\src\com\android\contacts\common\list
(1)configureOnlyShowPhoneContactsSelection函数中如下语句:
selection.append(Contacts.INDICATE_PHONE_SIM + "= ?");
selectionArgs.add("-1");
之后增加下面的代码
selection.append(" AND " + RawContacts.IS_SDN_CONTACT + " < 0");
2. File:Contact.java
Path: alps\packages\apps\ContactsCommon\src\com\android\contacts\common\model
增加如下函数:
//add by MTK---Preset Contacts
public boolean isReadOnlyContact() {
return mIsSdnContact == -2;
}
3. File:ContactLoaderFragment.java
Path:alps\packages\apps\contacts\src\com\android\contacts\detail
将isContactEditable函数修改为:
public boolean isContactEditable() {
return mContactData != null && !mContactData.isDirectoryEntry()&& !mContactData.isSdnContacts()&& !mContactData.is InternationalDialNumber()&& !mContactData.isReadOnlyContact() ;
}
4. File:ContactEntryListAdapter.java
Path:alps\packages\apps\contactscommon\src\com\android\contacts\common\list
在文件最后增加以下代码:
public boolean showReadOnlyContact = true;
public void setShowReadOnlyContact(boolean canDelete) {
showReadOnlyContact = canDelete;
}
5. File:ContactEntryListFragment.java
Path:alps\packages\apps\contactscommon\src\com\android\contacts\common\list
添加代码:
protected boolean isInstanceOfContactsMultiDeletionFragment(){
return false;
}
在onCreateLoader函数中,倒数第二句mAdapter.configureLoader(loader, directoryId);之前增加语句:
mAdapter.setShowReadOnlyContact(isInstanceOfContactsMultiDeletionFragment() ? false : true);
6. File: ContactsMultiDeletionFragment.java
Path:alps\packages\apps\Contacts\src\com\mediatek\contacts\list
添加代码:
protected boolean isInstanceOfContactsMultiDeletionFragment(){
return true;
}
7.File:MultiContactsBasePickerAdapter.java
Path:alps\packages\apps\contacts\src\com\mediatek\contacts\list
在configureSelection函数最后的语句
loader.setSelection(selection.toString());
之前增加语句:
if (!showReadOnlyContact ) {
selection.append(" AND " + Contacts.IS_SDN_CONTACT + "=0");
}
8.File:AggregationSuggestionEngine.java
Path:alps\packages\apps\contacts\src\com\android\contacts\editor
在loadAggregationSuggestions函数最后的语句
在语句:
sb.append(" AND " + Contacts.INDICATE_PHONE_SIM + "=-1");
之后添加:
sb.append(" AND " + Contacts.IS_SDN_CONTACT + "!=-2");
9.File:JoinContactListAdapter.java
Path:packages\apps\contacts\src\com\android\contacts\list
函数:public void configureLoader(CursorLoader cursorLoader, long directoryId)
将:
loader.setSelection(Contacts._ID + "!=?"+" AND " + Contacts.INDICATE_PHONE_SIM + "=-1");
修改为:
loader.setSelection(Contacts._ID + "!=?"+" AND " + Contacts.INDICATE_PHONE_SIM + "=-1" + " AND " + Contacts.IS_SDN_CONTACT + "!=-2");
10.File: ContactDetailPhotoSetter.java
Path:alps\packages\apps\contacts\src\com\android\contacts\detail
将onClick()函数修改为
@Override
public void onClick(View v) {
if(mContactData!= null && !mContactData.isContactEditable()){
// Assemble the intent.
RawContactDeltaList delta = mContactData.createRawContactDeltaList();
// Find location and bounds of target view, adjusting based on the
// assumed local density.
final float appScale =
mContext.getResources().getCompatibilityInfo().applicationScale;
final int[] pos = new int[2];
v.getLocationOnScreen(pos);
// rect is the bounds (in pixels) of the photo view in screen coordinates
final Rect rect = new Rect();
rect.left = (int) (pos[0] * appScale + 0.5f);
rect.top = (int) (pos[1] * appScale + 0.5f);
rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
Uri photoUri = null;
if (mContactData.getPhotoUri() != null) {
photoUri = Uri.parse(mContactData.getPhotoUri());
}
Intent photoSelectionIntent = PhotoSelectionActivity.buildIntent(mContext,
photoUri, mPhotoBitmap, mPhotoBytes, rect, delta, mContactData.isUserProfile(),
mContactData.isDirectoryEntry(), mExpandPhotoOnClick);
// Cache the bitmap directly, so the activity can pull it from the
// photo manager.
if (mPhotoBitmap != null) {
ContactPhotoManager.getInstance(mContext).cacheBitmap(
photoUri, mPhotoBitmap, mPhotoBytes);
}
mContext.startActivity(photoSelectionIntent);
}
}
JB7、JB9版本
Part2:导入预置联系人VCard文件
1. 修改SIMServiceUtils.java
Path:alps\packages\apps\Contacts\src\com\mediatek\contacts\simservice
添加
public static final int SERVICE_WORK_IMPORT_PRESET_CONTACTS = 5;
2. 修改SIMProcessorManager.java
Path:alps\packages\apps\Contacts\src\com\mediatek\contacts\simservice
在SIMProcessorManager.java中createProcessor函数里添加
else if (workType == SIMServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS) {
processor = new ImportPresetContactProcessor(context, ImportPresetContactProcessor.PRESET_CONTACT_VCARD, 0); }
4. 修改BootCmpReceiver.java
Path:alps\packages\apps\Contacts\src\com\mediatek\contacts\simcontact
在BootCmpReceiver.java中processBootComplete()方法最后添加代码
startSimService(-1, SIMServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS);
5. 新建ImportPresetContactProcessor.java
Path: alps\packages\apps\Contacts\src\com\mediatek\contacts\vcard
package com.mediatek.contacts.vcard;
import android.content.Context;
import android.accounts.Account;
import android.content.ContentResolver;
import android.net.Uri;
// The following lines are provided and maintained by Mediatek Inc.
import android.os.PowerManager;
import android.os.Process;
// The previous lines are provided and maintained by Mediatek Inc.
import android.util.Log;
import com.mediatek.contacts.ext.ContactPluginDefault;
import com.android.vcard.VCardEntryCounter;
import com.android.vcard.VCardSourceDetector;
import com.android.vcard.VCardEntry;
import com.android.vcard.VCardEntryCommitter;
import com.android.vcard.VCardEntryConstructor;
import com.android.vcard.VCardEntryHandler;
import com.android.vcard.VCardInterpreter;
import com.android.vcard.VCardParser;
import com.android.vcard.VCardParser_V21;
import com.android.vcard.VCardParser_V30;
import com.android.vcard.exception.VCardException;
import com.android.vcard.exception.VCardNestedException;
import com.android.vcard.exception.VCardNotSupportedException;
import com.android.vcard.exception.VCardVersionException;
import com.mediatek.contacts.ExtensionManager;
import com.android.contacts.common.vcard.ImportRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
//add by MTK for importing preset contact START
import com.mediatek.contacts.simservice.SIMServiceUtils;
import com.mediatek.contacts.simservice.SIMProcessorBase;
//add by MTK for importing preset contact END
/**
* Class for processing one import request from a user. Dropped after importing requested Uri(s).
* {@link VCardService} will create another object when there is another import request.
*/
public class ImportPresetContactProcessor extends SIMProcessorBase implements VCardEntryHandler {
/* package */ static final String VCARD_URI_ARRAY = "vcard_uri";
/* package */ static final String ESTIMATED_VCARD_TYPE_ARRAY = "estimated_vcard_type";
/* package */ static final String ESTIMATED_CHARSET_ARRAY = "estimated_charset";
/* package */ static final String VCARD_VERSION_ARRAY = "vcard_version";
/* package */ static final String ENTRY_COUNT_ARRAY = "entry_count";
/* package */ final static int VCARD_VERSION_AUTO_DETECT = 0;
/* package */ final static int VCARD_VERSION_V21 = 1;
/* package */ final static int VCARD_VERSION_V30 = 2;
private static final String LOG_TAG = "VCardImportPresetContact";
public static final String PRESET_CONTACT_VCARD =
"file:///android_asset/preset_contacts.vcf";
private static final boolean DEBUG = false;
private final Context mContext;
private final ContentResolver mResolver;
private final Uri mUri;
private final String mDisplayName;
private final int mJobId;
// TODO: remove and show appropriate message instead.
private final List<Uri> mFailedUris = new ArrayList<Uri>();
private VCardParser mVCardParser;
private volatile boolean mCanceled;
private volatile boolean mDone;
/*
* Change Feature by Mediatek Begin.
* Descriptions: handle cancel in the cancel function
*/
private volatile boolean mIsRunning = false;
/*
* Change Feature by Mediatek End.
*/
private int mCurrentCount = 0;
private int mTotalCount = 0;
/*
* New Feature by Mediatek Begin.
* Descriptions: All of process should not be stopped by PM.
*/
private PowerManager.WakeLock mWakeLock;
/*
* New Feature by Mediatek End.
*/
public ImportPresetContactProcessor(final Context context,
final String vcardFilePath, final int jobId) {
super(null, null);
mContext = context;
mResolver = mContext.getContentResolver();
mJobId = jobId;
mUri = Uri.parse(vcardFilePath);
mDisplayName = mUri.getLastPathSegment();
Log.d(LOG_TAG, "vCard Uri = " + mUri.getPath() + ", vCard DisplayName = " + mDisplayName);
/*
* New Feature by Mediatek Begin.
* Descriptions: All of process should not be stopped by PM.
*/
final PowerManager powerManager = (PowerManager) mContext.getApplicationContext()
.getSystemService("power");
mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK
| PowerManager.ON_AFTER_RELEASE, LOG_TAG);
/*
* New Feature by Mediatek End.
*/
Log.d(LOG_TAG, "ImportProcessor() ");
}
@Override
public void onStart() {
// do nothing
}
@Override
public void onEnd() {
Log.d(LOG_TAG, "onEnd() ");
// do nothing
}
@Override
public void onEntryCreated(VCardEntry entry) {
mCurrentCount++;
}
@Override
public final int getType() {
//add by MTK for importing preset contact START
//return VCardService.TYPE_IMPORT;
return SIMServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS;
//add by MTK for importing preset contact END
}
//add by MTK for importing preset contact START
public void doWork() {
Log.d(LOG_TAG,"ImportPresetContactProcessor, run()");
//add by MTK for importing preset contact END
/*
* Change Feature by Mediatek Begin.
* Descriptions: handle cancel in the cancel function
*/
mIsRunning = true;
/*
* Change Feature by Mediatek End.
*/
/*
* New Feature by Mediatek Begin.
* Descriptions: All of process should not be stopped by PM.
*/
mWakeLock.acquire();
/*
* New Feature by Mediatek End.
*/
// ExecutorService ignores RuntimeException, so we need to show it here.
try {
/*
* Bug Fix by Mediatek Begin.
* CR ID: ALPS00115856
* Descriptions: Set the thread priority to lowest
* and give up the change for Query
*/
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
/*
* Bug Fix by Mediatek End.
*/
runInternal();
} catch (OutOfMemoryError e) {
Log.e(LOG_TAG, "OutOfMemoryError thrown during import", e);
throw e;
} catch (RuntimeException e) {
Log.e(LOG_TAG, "RuntimeException thrown during import", e);
throw e;
} finally {
synchronized (this) {
mDone = true;
}
/*
* New Feature by Mediatek Begin.
* Descriptions: All of process should not be stopped by PM.
*/
if (mWakeLock != null && mWakeLock.isHeld()) {
mWakeLock.release();
}
/*
* New Feature by Mediatek End.
*/
}
}
private void runInternal() {
int count = context.getContentResolver().delete( RawContacts.CONTENT_URI.buildUpon().build(), RawContacts.IS_SDN_CONTACT + " = -2", null);
LogUtils.d(TAG,"[deletePresetContacts]count:" + count);
Log.i(LOG_TAG, String.format("vCard import (id: %d) has started.", mJobId));
final ImportRequest request = getImportRequest(mUri, mDisplayName);
if (isCancelled()) {
Log.i(LOG_TAG, "Canceled before actually handling parameter (" + request.uri + ")");
return;
}
final int[] possibleVCardVersions;
if (request.vcardVersion == VCARD_VERSION_AUTO_DETECT) {
/**
* Note: this code assumes that a given Uri is able to be opened more than once,
* which may not be true in certain conditions.
*/
possibleVCardVersions = new int[] {
VCARD_VERSION_V21,
VCARD_VERSION_V30
};
} else {
possibleVCardVersions = new int[] {
request.vcardVersion
};
}
final Uri uri = request.uri;
final Account account = request.account;
final int estimatedVCardType = request.estimatedVCardType;
final String estimatedCharset = request.estimatedCharset;
final int entryCount = request.entryCount;
mTotalCount += entryCount;
/**
* add for import/export group infomations feature . start
* original code:
* final VCardEntryConstructor constructor =
* new VCardEntryConstructor(estimatedVCardType, account, estimatedCharset);
*/
final VCardEntryConstructor constructor = new VCardEntryConstructorForPresetContact(
estimatedVCardType, account, estimatedCharset);
/**
* add for import/export group infomations feature . end
*/
final VCardEntryCommitter committer = new VCardEntryCommitter(mResolver);
constructor.addEntryHandler(committer);
constructor.addEntryHandler(this);
InputStream is = null;
boolean successful = false;
try {
if (uri != null) {
Log.i(LOG_TAG, "start importing one vCard (Uri: " + uri + ")");
is = mContext.getAssets().open(uri.getLastPathSegment());
//is = mResolver.openInputStream(uri);
} else if (request.data != null){
Log.i(LOG_TAG, "start importing one vCard (byte[])");
is = new ByteArrayInputStream(request.data);
}
if (is != null) {
successful = readOneVCard(is, estimatedVCardType, estimatedCharset, constructor,
possibleVCardVersions);
}
} catch (IOException e) {
successful = false;
} finally {
if (is != null) {
try {
is.close();
} catch (Exception e) {
// ignore
}
}
}
if (successful) {
// TODO: successful becomes true even when cancelled. Should return more appropriate
// value
if (isCancelled()) {
Log.i(LOG_TAG, "vCard import has been canceled (uri: " + uri + ")");
// Cancel notification will be done outside this method.
} else {
Log.i(LOG_TAG, "Successfully finished importing one vCard file: " + uri);
List<Uri> uris = committer.getCreatedUris();
}
} else {
Log.w(LOG_TAG, "Failed to read one vCard file: " + uri);
mFailedUris.add(uri);
}
}
private boolean readOneVCard(InputStream is, int vcardType, String charset,
final VCardInterpreter interpreter,
final int[] possibleVCardVersions) {
boolean successful = false;
final int length = possibleVCardVersions.length;
for (int i = 0; i < length; i++) {
final int vcardVersion = possibleVCardVersions[i];
try {
if (i > 0 && (interpreter instanceof VCardEntryConstructor)) {
// Let the object clean up internal temporary objects,
((VCardEntryConstructor) interpreter).clear();
}
// We need synchronized block here,
// since we need to handle mCanceled and mVCardParser at once.
// In the worst case, a user may call cancel() just before creating
// mVCardParser.
synchronized (this) {
mVCardParser = (vcardVersion == VCARD_VERSION_V30 ?
new VCardParser_V30(vcardType) :
new VCardParser_V21(vcardType));
if (isCancelled()) {
Log.i(LOG_TAG, "ImportProcessor already recieves cancel request, so " +
"send cancel request to vCard parser too.");
mVCardParser.cancel();
}
}
mVCardParser.parse(is, interpreter);
successful = true;
break;
} catch (IOException e) {
Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
} catch (VCardNestedException e) {
// This exception should not be thrown here. We should instead handle it
// in the preprocessing session in ImportVCardActivity, as we don't try
// to detect the type of given vCard here.
//
// TODO: Handle this case appropriately, which should mean we have to have
// code trying to auto-detect the type of given vCard twice (both in
// ImportVCardActivity and ImportVCardService).
Log.e(LOG_TAG, "Nested Exception is found.");
} catch (VCardNotSupportedException e) {
Log.e(LOG_TAG, e.toString());
} catch (VCardVersionException e) {
if (i == length - 1) {
Log.e(LOG_TAG, "Appropriate version for this vCard is not found.");
} else {
// We'll try the other (v30) version.
}
} catch (VCardException e) {
Log.e(LOG_TAG, e.toString());
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
return successful;
}
@Override
public synchronized boolean cancel(boolean mayInterruptIfRunning) {
if (DEBUG) Log.d(LOG_TAG, "ImportProcessor received cancel request");
if (mDone || mCanceled) {
return false;
}
mCanceled = true;
synchronized (this) {
if (mVCardParser != null) {
mVCardParser.cancel();
}
}
return true;
}
@Override
public synchronized boolean isCancelled() {
return mCanceled;
}
@Override
public synchronized boolean isDone() {
return mDone;
}
private ImportRequest getImportRequest(final Uri localDataUri, final String displayName){
final ImportRequest request;
try {
request = constructImportRequest(null, localDataUri, displayName);
} catch (VCardException e) {
Log.e(LOG_TAG, "Maybe the file is in wrong format", e);
///fixed cr ALPS00598462
return null;
/*
* Bug Fix by Mediatek Begin. CR ID: ALPS00318987
*/
} catch (IllegalArgumentException e) {
Log.e(LOG_TAG, "Maybe the file is in wrong format", e);
///fixed cr ALPS00598462
return null;
/*
* Bug Fix by Mediatek End.
*/
} catch (IOException e) {
Log.e(LOG_TAG, "Unexpected IOException", e);
///fixed cr ALPS00598462
return null;
}
return request;
}
private ImportRequest constructImportRequest(final byte[] data,
final Uri localDataUri, final String displayName)
throws IOException, VCardException {
final ContentResolver resolver = mContext.getApplicationContext().getContentResolver();
VCardEntryCounter counter = null;
VCardSourceDetector detector = null;
int vcardVersion = VCARD_VERSION_V21;
try {
boolean shouldUseV30 = false;
InputStream is;
if (data != null) {
is = new ByteArrayInputStream(data);
} else {
is = mContext.getAssets().open(localDataUri.getLastPathSegment());
//is = resolver.openInputStream(localDataUri);
}
mVCardParser = new VCardParser_V21();
try {
counter = new VCardEntryCounter();
detector = new VCardSourceDetector();
mVCardParser.addInterpreter(counter);
mVCardParser.addInterpreter(detector);
mVCardParser.parse(is);
} catch (VCardVersionException e1) {
try {
is.close();
} catch (IOException e) {
}
shouldUseV30 = true;
if (data != null) {
is = new ByteArrayInputStream(data);
} else {
is = mContext.getAssets().open(localDataUri.getLastPathSegment());
//is = resolver.openInputStream(localDataUri);
}
mVCardParser = new VCardParser_V30();
try {
counter = new VCardEntryCounter();
detector = new VCardSourceDetector();
mVCardParser.addInterpreter(counter);
mVCardParser.addInterpreter(detector);
mVCardParser.parse(is);
} catch (VCardVersionException e2) {
throw new VCardException("vCard with unspported version.");
}
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
vcardVersion = shouldUseV30 ? VCARD_VERSION_V30 : VCARD_VERSION_V21;
} catch (VCardNestedException e) {
Log.w(LOG_TAG, "Nested Exception is found (it may be false-positive).");
// Go through without throwing the Exception, as we may be able to detect the
// version before it
}
return new ImportRequest(null,
data, localDataUri, displayName,
detector.getEstimatedType(),
detector.getEstimatedCharset(),
vcardVersion, counter.getCount());
}
}
6. 新建VCardEntryConstructorForPresetContact.java
path: alps\packages\apps\Contacts\src\com\mediatek\contacts\vcard
package com.mediatek.contacts.vcard;
import android.accounts.Account;
import com.android.vcard.VCardConfig;
import com.android.vcard.VCardEntryConstructor;
import com.android.vcard.VCardEntry;
public class VCardEntryConstructorForPresetContact extends VCardEntryConstructor {
private final int mVCardType;
private final Account mAccount;
public VCardEntryConstructorForPresetContact() {
this(VCardConfig.VCARD_TYPE_V21_GENERIC, null, null);
}
public VCardEntryConstructorForPresetContact(final int vcardType) {
this(vcardType, null, null);
}
public VCardEntryConstructorForPresetContact(final int vcardType, final Account account) {
this(vcardType, account, null);
}
/**
* @deprecated targetCharset is not used anymore.
* Use {@link #VCardEntryConstructor(int, Account)}
*/
@Deprecated
public VCardEntryConstructorForPresetContact(final int vcardType, final Account account,
String targetCharset) {
mVCardType = vcardType;
mAccount = account;
}
protected VCardEntry getVCardEntry() {
return new VCardEntryForPresetContact(mVCardType, mAccount);
}
}
7. 新建VCardEntryForPresetContact.java
path: alps\packages\apps\Contacts\src\com\mediatek\contacts\vcard
package com.mediatek.contacts.vcard;
import com.android.vcard.VCardEntry;
import android.accounts.Account;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.provider.ContactsContract.RawContacts;
import java.util.ArrayList;
import java.util.List;
import com.android.contacts.common.model.account.AccountType;
public class VCardEntryForPresetContact extends VCardEntry{
public VCardEntryForPresetContact(int vcardType, Account account){
super(vcardType, account);
}
@Override
public ArrayList<ContentProviderOperation> constructInsertOperations(ContentResolver resolver,
ArrayList<ContentProviderOperation> operationList) {
if (operationList == null) {
operationList = new ArrayList<ContentProviderOperation>();
}
if (isIgnorable()) {
return operationList;
}
final int backReferenceIndex = operationList.size();
// After applying the batch the first result's Uri is returned so it is important that
// the RawContact is the first operation that gets inserted into the list.
ContentProviderOperation.Builder builder = ContentProviderOperation
.newInsert(RawContacts.CONTENT_URI);
builder.withValue(RawContacts.ACCOUNT_NAME, AccountType.ACCOUNT_NAME_LOCAL_PHONE);
builder.withValue(RawContacts.ACCOUNT_TYPE, AccountType.ACCOUNT_TYPE_LOCAL_PHONE);
builder.withValue(RawContacts.INDICATE_PHONE_SIM, RawContacts.INDICATE_PHONE);
builder.withValue(RawContacts.IS_SDN_CONTACT, -2);
operationList.add(builder.build());
int start = operationList.size();
iterateAllData(new InsertOperationConstrutor(operationList, backReferenceIndex));
int end = operationList.size();
return operationList;
}
private class InsertOperationConstrutor implements EntryElementIterator {
private final List<ContentProviderOperation> mOperationList;
private final int mBackReferenceIndex;
public InsertOperationConstrutor(List<ContentProviderOperation> operationList,
int backReferenceIndex) {
mOperationList = operationList;
mBackReferenceIndex = backReferenceIndex;
}
@Override
public void onIterationStarted() {
}
@Override
public void onIterationEnded() {
}
@Override
public void onElementGroupStarted(EntryLabel label) {
}
@Override
public void onElementGroupEnded() {
}
@Override
public boolean onElement(EntryElement elem) {
if (!elem.isEmpty()) {
elem.constructInsertOperation(mOperationList, mBackReferenceIndex);
}
return true;
}
}
}
displayName = sourceUri.getLastPathSegment();
private ImportRequest constructImportRequest(final byte[] data,
final Uri localDataUri, final String displayName)
throws IOException, VCardException {
final ContentResolver resolver = ImportVCardActivity.this.getContentResolver();
VCardEntryCounter counter = null;
VCardSourceDetector detector = null;
int vcardVersion = VCARD_VERSION_V21;
try {
boolean shouldUseV30 = false;
InputStream is;
if (data != null) {
is = new ByteArrayInputStream(data);
} else {
is = resolver.openInputStream(localDataUri);
}
mVCardParser = new VCardParser_V21();
try {
counter = new VCardEntryCounter();
detector = new VCardSourceDetector();
mVCardParser.addInterpreter(counter);
mVCardParser.addInterpreter(detector);
mVCardParser.parse(is);
} catch (VCardVersionException e1) {
try {
is.close();
} catch (IOException e) {
}
shouldUseV30 = true;
if (data != null) {
is = new ByteArrayInputStream(data);
} else {
is = resolver.openInputStream(localDataUri);
}
mVCardParser = new VCardParser_V30();
try {
counter = new VCardEntryCounter();
detector = new VCardSourceDetector();
mVCardParser.addInterpreter(counter);
mVCardParser.addInterpreter(detector);
mVCardParser.parse(is);
} catch (VCardVersionException e2) {
throw new VCardException("vCard with unspported version.");
}
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
vcardVersion = shouldUseV30 ? VCARD_VERSION_V30 : VCARD_VERSION_V21;
} catch (VCardNestedException e) {
Log.w(LOG_TAG, "Nested Exception is found (it may be false-positive).");
// Go through without throwing the Exception, as we may be able to detect the
// version before it
}
return new ImportRequest(mAccount,
data, localDataUri, displayName,
detector.getEstimatedType(),
detector.getEstimatedCharset(),
vcardVersion, counter.getCount());
}
Part3:保证预置联系人不可编辑
1. File:DefaultContactListAdapter.java
Path: alps\packages\apps\Contacts\src\com\android\contacts\list
(1)configureOnlyShowPhoneContactsSelection函数中如下语句:
selection.append(Contacts.INDICATE_PHONE_SIM + "= ?");
selectionArgs.add("-1");
之后增加下面的代码
selection.append(" AND " + RawContacts.IS_SDN_CONTACT + " < 0");
2. File:Contact.java
Path: alps\packages\apps\Contacts\src\com\android\contacts\model
增加如下函数:
//add by MTK---Preset Contacts
public boolean isReadOnlyContact() {
return mIsSdnContact == -2;
}
3. File:ContactLoaderFragment.java
Path:alps\packages\apps\contacts\src\com\android\contacts\detail
将isContactEditable函数修改为:
public boolean isContactEditable() {
return mContactData != null && !mContactData.isDirectoryEntry()&& !mContactData.isSdnContacts()&& !mContactData.is InternationalDialNumber()&& !mContactData.isReadOnlyContact() ;
}
4. File:ContactEntryListAdapter.java
Path:alps\packages\apps\contacts\src\com\android\contacts\list
在文件最后增加以下代码:
public boolean showReadOnlyContact = true;
public void setShowReadOnlyContact(boolean canDelete) {
showReadOnlyContact = canDelete;
}
5. File:ContactEntryListFragment.java
Path:alps\packages\apps\contacts\src\com\android\contacts\list
添加代码:
protected boolean isInstanceOfContactsMultiDeletionFragment(){
return false;
}
在onCreateLoader函数中,倒数第二句
mAdapter.configureLoader(loader, directoryId);
之前增加语句:
mAdapter.setShowReadOnlyContact(isInstanceOfContactsMultiDeletionFragment() ? false : true);
6. File: ContactsMultiDeletionFragment.java
Path:alps\packages\apps\Contacts\src\com\mediatek\contacts\list
添加代码:
protected boolean isInstanceOfContactsMultiDeletionFragment(){
return true;
}
7.File:MultiContactsBasePickerAdapter.java
Path:alps\packages\apps\contacts\src\com\mediatek\contacts\list
在configureSelection函数最后的语句
loader.setSelection(selection.toString());
之前增加语句:
if (!showReadOnlyContact ) {
selection.append(" AND " + Contacts.IS_SDN_CONTACT + "=0");
}
8.File:AggregationSuggestionEngine.java
Path:alps\packages\apps\contacts\src\com\android\contacts\editor
在loadAggregationSuggestions函数最后的语句
在语句:
sb.append(" AND " + Contacts.INDICATE_PHONE_SIM + "=-1");
之后添加:
sb.append(" AND " + Contacts.IS_SDN_CONTACT + "!=-2");
9.File:JoinContactListAdapter.java
Path:packages\apps\contacts\src\com\android\contacts\list
函数:public void configureLoader(CursorLoader cursorLoader, long directoryId)
将:
loader.setSelection(Contacts._ID + "!=?"+" AND " + Contacts.INDICATE_PHONE_SIM + "=-1");
修改为:
loader.setSelection(Contacts._ID + "!=?"+" AND " + Contacts.INDICATE_PHONE_SIM + "=-1" + " AND " + Contacts.IS_SDN_CONTACT + "!=-2");