Android---操作SIM卡對聯絡人的影響(二)
前言
今天整理一下關於在Contacts中通過Setting進行Import/Export SIM卡聯絡人的過程。同樣,原始碼分析基於Android8高通平臺。
Import過程
Import過程是指將SIM卡中的聯絡人匯入(copy)到本地賬號中,需要強調的一點是,當裝置未登陸google賬號之前,存入手機本地的聯絡人的Account為null,如果登陸了google賬號,存入本地的聯絡人的Account變為com.google,並且之前存入的聯絡人的Account也會變為新的google賬號。先簡單介紹一下操作手法:開啟Contacts.apk ->功能按鈕 ->Settings ->Import ->選擇SIM card ->勾選需要import的聯絡人 -> IMPORT ->彈出Toast&傳送通知表示copy成功。 程式碼邏輯如下:
final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final int resId = adapter.getItem(which).mChoiceResourceId; if (resId == R.string.import_from_sim) { handleSimImportRequest(adapter.getItem(which).mSim); } else if (resId == R.string.import_from_vcf_file) { handleImportRequest(resId, SimCard.NO_SUBSCRIPTION_ID); } else { Log.e(TAG, "Unexpected resource: "+ getActivity().getResources().getResourceEntryName(resId)); } dialog.dismiss(); } };
會彈出對話方塊供你選擇,從.vcf檔案中copy聯絡人還是從SIM卡中copy,本文主要介紹從SIM卡中copy聯絡人。
private void handleSimImportRequest(SimCard sim) {
startActivity(new Intent(getActivity(), SimImportActivity.class)
.putExtra(SimImportActivity.EXTRA_SUBSCRIPTION_ID, sim.getSubscriptionId()));
}
在SimImportActivity.class中構建一個fragment(SimImportFragment.java),其中包含個ListView和幾個操作功能按鈕。ListView設定為多選模式setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);勾選了需要copy的聯絡人之後,點選Import按鈕。
mImportButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
importCurrentSelections();
// Do we wait for import to finish?
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish();
}
});
private void importCurrentSelections() {
final SparseBooleanArray checked = mListView.getCheckedItemPositions();
final ArrayList<SimContact> importableContacts = new ArrayList<>(checked.size());
for (int i = 0; i < checked.size(); i++) {
// It's possible for existing contacts to be "checked" but we only want to import the ones that don't already exist
if (checked.valueAt(i) && !mAdapter.existsInCurrentAccount(i)) {
importableContacts.add(mAdapter.getItem(checked.keyAt(i)));
}
}
SimImportService.startImport(getContext(), mSubscriptionId, importableContacts,
mAccountHeaderPresenter.getCurrentAccount());
}
packages/apps/Contacts/src/com/android/contacts/SimImportService.java
public static void startImport(Context context, int subscriptionId,
ArrayList<SimContact> contacts, AccountWithDataSet targetAccount) {
context.startService(new Intent(context, SimImportService.class)
.putExtra(EXTRA_SIM_CONTACTS, contacts) //選中的聯絡人
.putExtra(EXTRA_SIM_SUBSCRIPTION_ID, subscriptionId)
.putExtra(EXTRA_ACCOUNT, targetAccount));
}
@Override
public int onStartCommand(Intent intent, int flags, final int startId) {
ContactsNotificationChannelsUtil.createDefaultChannel(this);
final ImportTask task = createTaskForIntent(intent, startId);
if (task == null) {
new StopTask(this, startId).executeOnExecutor(mExecutor);
return START_NOT_STICKY;
}
sPending.add(task);
task.executeOnExecutor(mExecutor);
notifyStateChanged();
return START_REDELIVER_INTENT;
}
配置好ImportTask並開始執行
@Override
protected Boolean doInBackground(Void... params) {
final TimingLogger timer = new TimingLogger(TAG, "import");
try {
// Just import them all at once.
// Experimented with using smaller batches (e.g. 25 and 50) so that percentage
// progress could be displayed however this slowed down the import by over a factor
// of 2. If the batch size is over a 100 then most cases will only require a single
// batch so we don't even worry about displaying accurate progress
mDao.importContacts(mContacts, mTargetAccount);
mDao.persistSimState(mSim.withImportedState(true));
timer.addSplit("done");
timer.dumpToLog();
} catch (RemoteException|OperationApplicationException e) {
FeedbackHelper.sendFeedback(SimImportService.this, TAG,
"Failed to import contacts from SIM card", e);
return false;
}
return true;
}
packages/apps/Contacts/src/com/android/contacts/database/SimContactDaoImpl.java
@Override
public ContentProviderResult[] importContacts(List<SimContact> contacts,
AccountWithDataSet targetAccount) throws RemoteException, OperationApplicationException {
if (contacts.size() < IMPORT_MAX_BATCH_SIZE) {
return importBatch(contacts, targetAccount);
}
final List<ContentProviderResult> results = new ArrayList<>();
for (int i = 0; i < contacts.size(); i += IMPORT_MAX_BATCH_SIZE) {
results.addAll(Arrays.asList(importBatch(
contacts.subList(i, Math.min(contacts.size(), i + IMPORT_MAX_BATCH_SIZE)),targetAccount)));
}
return results.toArray(new ContentProviderResult[results.size()]);
}
private ContentProviderResult[] importBatch(List<SimContact> contacts,
AccountWithDataSet targetAccount) throws RemoteException, OperationApplicationException {
final ArrayList<ContentProviderOperation> ops = createImportOperations(contacts, targetAccount);
return mResolver.applyBatch(ContactsContract.AUTHORITY, ops); //執行事務化處理
}
在程式碼中createImportOperations()方法中具體決定了對資料庫執行哪種操作
private ArrayList<ContentProviderOperation> createImportOperations(List<SimContact> contacts,
AccountWithDataSet targetAccount) {
final ArrayList<ContentProviderOperation> ops = new ArrayList<>();
for (SimContact contact : contacts) {
contact.appendCreateContactOperations(ops, targetAccount);
}
return ops;
}
packages/apps/Contacts/src/com/android/contacts/model/SimContact.java
public void appendCreateContactOperations(List<ContentProviderOperation> ops,
AccountWithDataSet targetAccount) {
// There is nothing to save so skip it.
if (!hasName() && !hasPhone() && !hasEmails() && !hasAnrs()) return;
final int rawContactOpIndex = ops.size();
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) //建立插入資料庫操作
.withYieldAllowed(true)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, targetAccount.name)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, targetAccount.type)
.withValue(ContactsContract.RawContacts.DATA_SET, targetAccount.dataSet)
.build());
if (mName != null) {
ops.add(createInsertOp(rawContactOpIndex, StructuredName.CONTENT_ITEM_TYPE,
StructuredName.DISPLAY_NAME, mName));
}
if (!mPhone.isEmpty()) {
ops.add(createInsertOp(rawContactOpIndex, Phone.CONTENT_ITEM_TYPE,
Phone.NUMBER, mPhone));
}
if (mEmails != null) {
for (String email : mEmails) {
ops.add(createInsertOp(rawContactOpIndex, Email.CONTENT_ITEM_TYPE,
Email.ADDRESS, email));
}
}
if (mAnrs != null) {
for (String anr : mAnrs) {
ops.add(createInsertOp(rawContactOpIndex, Phone.CONTENT_ITEM_TYPE,
Phone.NUMBER, anr));
}
}
}
至此,SIM卡中被選中的聯絡人已經import到本地的聯絡人賬戶中,ImportTask執行完畢後,將會有一些通知表明操作是否成功。
packages/apps/Contacts/src/com/android/contacts/SimImportService.java
@Override
protected void onPostExecute(Boolean success) {
super.onPostExecute(success);
stopSelf(mStartId);
Intent result;
final Notification notification;
if (success) {
result = new Intent(BROADCAST_SIM_IMPORT_COMPLETE)
.putExtra(EXTRA_RESULT_CODE, RESULT_SUCCESS)
.putExtra(EXTRA_RESULT_COUNT, mContacts.size())
.putExtra(EXTRA_OPERATION_REQUESTED_AT_TIME, mStartTime)
.putExtra(EXTRA_SIM_SUBSCRIPTION_ID, mSim.getSubscriptionId());
notification = getCompletedNotification(); //傳送import成功的通知
} else {
result = new Intent(BROADCAST_SIM_IMPORT_COMPLETE)
.putExtra(EXTRA_RESULT_CODE, RESULT_FAILURE)
.putExtra(EXTRA_OPERATION_REQUESTED_AT_TIME, mStartTime)
.putExtra(EXTRA_SIM_SUBSCRIPTION_ID, mSim.getSubscriptionId());
notification = getFailedNotification(); //傳送import失敗的通知
}
LocalBroadcastManager.getInstance(SimImportService.this).sendBroadcast(result); //傳送相關狀態的廣播
sPending.remove(this);
// Only notify of completion if all the import requests have finished. We're using
// the same notification for imports so in the rare case that a user has started
// multiple imports the notification won't go away until all of them complete.
if (sPending.isEmpty()) {
stopForeground(false);
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
notifyStateChanged();
}
packages/apps/Contacts/src/com/android/contacts/preference/DisplayOptionsPreferenceFragment.java
private class SaveServiceResultListener extends BroadcastReceiver {//接收廣播
@Override
public void onReceive(Context context, Intent intent) {
final long now = System.currentTimeMillis();
final long opStart = intent.getLongExtra(
SimImportService.EXTRA_OPERATION_REQUESTED_AT_TIME, now);
// If it's been over 30 seconds the user is likely in a different context so suppress the toast message.
if (now - opStart > 30*1000) return;
final int code = intent.getIntExtra(SimImportService.EXTRA_RESULT_CODE,
SimImportService.RESULT_UNKNOWN);
final int count = intent.getIntExtra(SimImportService.EXTRA_RESULT_COUNT, -1);
if (code == SimImportService.RESULT_SUCCESS && count > 0) {
Snackbar.make(mRootView, getResources().getQuantityString(
R.plurals.sim_import_success_toast_fmt, count, count),
Snackbar.LENGTH_LONG).show(); //彈出import成功的Toast
} else if (code == SimImportService.RESULT_FAILURE) {
Snackbar.make(mRootView, R.string.sim_import_failed_toast,
Snackbar.LENGTH_LONG).show(); //彈出import失敗的Toast
}
}
}
Export
是指將本地的聯絡人Export到SIM卡中,也就是說將裝置中的聯絡人copy到SIM卡中,成功後傳送Toast通知使用者,照慣例先貼上邏輯流程圖: 在點選Export選項之前的邏輯是一樣的,我們看看之後的程式碼:
else if (KEY_EXPORT.equals(prefKey)) {
ExportDialogFragment.show(getFragmentManager(), ContactsPreferenceActivity.class,
ExportDialogFragment.EXPORT_MODE_ALL_CONTACTS);
return true;
}
packages/apps/Contacts/src/com/android/contacts/interactions/ExportDialogFragment.java
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
//......省略的程式碼
final DialogInterface.OnClickListener clickListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
boolean dismissDialog;
final int resId = adapter.getItem(which).mChoiceResourceId;
if (resId == R.string.export_to_vcf_file) { //Export 聯絡人 to Vcard
dismissDialog = true;
final Intent exportIntent = new Intent(getActivity(), ExportVCardActivity.class);
exportIntent.putExtra(VCardCommonArguments.ARG_CALLING_ACTIVITY,callingActivity);
getActivity().startActivity(exportIntent);
} else if (resId == R.string.share_contacts) { //分享聯絡人
dismissDialog = true;
if (mExportMode == EXPORT_MODE_FAVORITES) {
doShareFavoriteContacts();
} else { // EXPORT_MODE_ALL_CONTACTS
final Intent exportIntent = new Intent(getActivity(), ShareVCardActivity.class);
exportIntent.putExtra(VCardCommonArguments.ARG_CALLING_ACTIVITY,callingActivity);
getActivity().startActivity(exportIntent);
}
} else if (resId == R.string.export_to_sim) { //Export 聯絡人 to SIM
dismissDialog = true;
int sub = adapter.getItem(which).mSim.getSubscriptionId();
Intent pickContactIntent = new Intent(
SimContactsConstants.ACTION_MULTI_PICK_CONTACT,Contacts.CONTENT_URI);
pickContactIntent.putExtra("exportSub", sub);
getActivity().startActivity(pickContactIntent);
} else {
dismissDialog = true;
Log.e(TAG, "Unexpected resource: "
+ getActivity().getResources().getResourceEntryName(resId));
}
if (dismissDialog) {
dialog.dismiss();
}
}
};
//......省略的程式碼
}
點選Export contacts SIM card選項,會進入到選擇要匯入的聯絡人頁面MultiPickContactsActivity.java,勾選需要Export的聯絡人後,點選OK按鈕:
@Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.btn_ok:
if (mPickMode.isSearchMode()) {
exitSearchMode();
}
if (mDelete) {
showDialog(R.id.dialog_delete_contact_confirmation);
} else if(mExportSub > -1) {
new ExportToSimThread().start();
}
//......省略的程式碼
}
}
public class ExportToSimThread extends Thread {
private int slot; //SIM卡數
private boolean canceled = false;
private int freeSimCount = 0; //SIM還可儲存的人數
private ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
private Account account;
final int BATCH_INSERT_NUMBER = 400; //可Export的最大人數
public ExportToSimThread() {
slot = ContactUtils.getActiveSlotId(mContext, mExportSub);
account = ContactUtils.getAcount(mContext, slot);
showExportProgressDialog();
}
@Override
public void run() {
boolean isSimCardFull = false;
// in case export is stopped, record the count of inserted successfully
int insertCount = 0;
freeSimCount = ContactUtils.getSimFreeCount(mContext, slot);
boolean canSaveAnr = ContactUtils.canSaveAnr(mContext, slot); //SIM卡中是否還可再存入Anr
boolean canSaveEmail = ContactUtils.canSaveEmail(mContext, slot); //SIM卡中是否還可再存入Email
int emailCountInOneSimContact = ContactUtils.getOneSimEmailCount(mContext, slot);
int phoneCountInOneSimContact = ContactUtils.getOneSimAnrCount(mContext, slot) + 1;
int emptyAnr = ContactUtils.getSpareAnrCount(mContext, slot);
int emptyEmail = ContactUtils.getSpareEmailCount(mContext, slot);
int emptyNumber = freeSimCount + emptyAnr;
Log.d(TAG, "freeSimCount = " + freeSimCount);
Bundle choiceSet = (Bundle) mChoiceSet.clone(); //獲得bundle中的資料
Set<String> set = choiceSet.keySet();//獲得bundle中的資料的鍵值對
Iterator<String> i = set.iterator();
while (i.hasNext() && !canceled) {
String id = String.valueOf(i.next());
String name = "";
ArrayList<String> arrayNumber = new ArrayList<String>();
ArrayList<String> arrayEmail = new ArrayList<String>();
Uri dataUri = Uri.withAppendedPath(
ContentUris.withAppendedId(Contacts.CONTENT_URI,Long.parseLong(id)),
Contacts.Data.CONTENT_DIRECTORY);
final String[] projection = new String[] { Contacts._ID,Contacts.Data.MIMETYPE, Contacts.Data.DATA1, };
Cursor c = mContext.getContentResolver().query(dataUri,projection, null, null, null);
try {
if (c != null && c.moveToFirst()) {
do {
String mimeType = c.getString(1);
if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) { //獲取name
name = c.getString(2);
}
if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) { //獲取number
String number = c.getString(2);
if (!TextUtils.isEmpty(number)&& emptyNumber-- > 0) {
arrayNumber.add(number);
}
}
if (canSaveEmail) {
if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) { //獲取Email
String email = c.getString(2);
if (!TextUtils.isEmpty(email)&& emptyEmail-- > 0) {
arrayEmail.add(email);
}
}
}
} while (c.moveToNext());
}
} finally {
if (c != null) {
c.close();
}
}
if (freeSimCount > 0 && 0 == arrayNumber.size()&& 0 == arrayEmail.size()) {
mToastHandler.sendMessage(mToastHandler.obtainMessage(
TOAST_EXPORT_NO_PHONE_OR_EMAIL, name));
continue;
}
int nameCount = (name != null && !name.equals("")) ? 1 : 0;
int groupNumCount = (arrayNumber.size() % phoneCountInOneSimContact) != 0 ?
(arrayNumber.size() / phoneCountInOneSimContact + 1)
: (arrayNumber.size() / phoneCountInOneSimContact);
int groupEmailCount = emailCountInOneSimContact == 0 ? 0
: ((arrayEmail.size() % emailCountInOneSimContact) != 0 ? (arrayEmail
.size() / emailCountInOneSimContact + 1)
: (arrayEmail.size() / emailCountInOneSimContact));
// recalute the group when spare anr is not enough
if (canSaveAnr && emptyAnr >= 0 && emptyAnr <= groupNumCount) {
groupNumCount = arrayNumber.size() - emptyAnr;
}
int groupCount = Math.max(groupEmailCount,
Math.max(nameCount, groupNumCount));
Uri result = null;
for (int k = 0; k < groupCount; k++) {
if (freeSimCount > 0) {
String num = arrayNumber.size() > 0 ? arrayNumber.remove(0) : null;
StringBuilder anrNum = new StringBuilder();
StringBuilder email = new StringBuilder();
if (canSaveAnr) {
for (int j = 1; j < phoneCountInOneSimContact; j++) {
if (arrayNumber.size() > 0 && emptyAnr-- > 0) {
String s = arrayNumber.remove(0);
anrNum.append(s);
anrNum.append(SimContactsConstants.ANR_SEP);
}
}
}
if (canSaveEmail) {
for (int j = 0; j < emailCountInOneSimContact; j++) {
if (arrayEmail.size() > 0) {
String s = arrayEmail.remove(0);
email.append(s);
email.append(SimContactsConstants.EMAIL_SEP);
}
}
}
result = ContactUtils.insertToCard(mContext, name,
num, email.toString(), anrNum.toString(), slot, false); //將選中聯絡人存入SIM卡中
if (null == result) {
// Failed to insert to SIM card
int anrNumber = 0;
if (!TextUtils.isEmpty(anrNum)) {
anrNumber += anrNum.toString().split(
SimContactsConstants.ANR_SEP).length;
}
emptyAnr += anrNumber;
emptyNumber += anrNumber;
if (!TextUtils.isEmpty(num)) {
emptyNumber++;
}
if (!TextUtils.isEmpty(email)) {
emptyEmail += email.toString().split(SimContactsConstants.EMAIL_SEP).length;
}
mToastHandler.sendMessage(mToastHandler.obtainMessage(
TOAST_SIM_EXPORT_FAILED, new String[] { name, num, email.toString() }));
} else {
insertCount++;
freeSimCount--;
batchInsert(name, num, anrNum.toString(),email.toString()); //同時將資料新增到資料庫中
}
} else {
isSimCardFull = true;
mToastHandler.sendMessage(mToastHandler.obtainMessage(TOAST_SIM_CARD_FULL, insertCount, 0));
break;
}
}
if (isSimCardFull) { //如果SIM卡中儲存已滿,直接break
break;
}
}
if (operationList.size() > 0) {
try {
mContext.getContentResolver().applyBatch(
android.provider.ContactsContract.AUTHORITY,operationList); //批量執行事務
} catch (Exception e) {
Log.e(TAG,String.format("%s: %s", e.toString(),e.getMessage()));
} finally {
operationList.clear();
}
}
if (!isSimCardFull) {
// if canceled, show toast indicating export is interrupted.
if (canceled) {
mToastHandler.sendMessage(mToastHandler.obtainMessage(TOAST_EXPORT_CANCELED,insertCount, 0));
} else {
mToastHandler.sendEmptyMessage(TOAST_EXPORT_FINISHED); //傳送Toast提示使用者Export完成
}
}
finish();
}
}
ExportToSimThread 執行緒中主要完成兩件事:一將勾選的聯絡人的資訊Copy到SIM卡中;二將這些聯絡人的設定為SIM卡聯絡人的格式(僅含有姓名,電話,email,ANR欄位且滿組欄位長度要求),並存入資料庫中:
private void batchInsert(String name, String phoneNumber, String anrs,String emailAddresses) {
final String[] emailAddressArray;
final String[] anrArray;
if (!TextUtils.isEmpty(emailAddresses)) {
emailAddressArray = emailAddresses.split(",");
} else {
emailAddressArray = null;
}
if (!TextUtils.isEmpty(anrs)) {
anrArray = anrs.split(SimContactsConstants.ANR_SEP);
} else {
anrArray = null;
}
Log.d(TAG, "insertToPhone: name= " + name + ", phoneNumber= " + phoneNumber
+ ", emails= " + emailAddresses + ", anrs= " + anrs + ", account= " + account);
//建立用於執行插入操作的Builder
ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
int ref = operationList.size();
if (account != null) { //Builder中新增Account型別
builder.withValue(RawContacts.ACCOUNT_NAME, account.name);
builder.withValue(RawContacts.ACCOUNT_TYPE, account.type);
}
operationList.add(builder.build()); //Builder新增到operationList事務中
// do not allow empty value insert into database.
if (!TextUtils.isEmpty(name)) { //Builder中新增聯絡人姓名
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, ref);
builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
builder.withValue(StructuredName.GIVEN_NAME, name);
operationList.add(builder.build()); //Builder新增到operationList事務中
}
if (!TextUtils.isEmpty(phoneNumber)) { //Builder中新增聯絡人電話
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Phone.RAW_CONTACT_ID, ref);
builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
builder.withValue(Phone.NUMBER, phoneNumber);
builder.withValue(Data.IS_PRIMARY, 1);
operationList.add(builder.build()); //Builder新增到operationList事務中
}
if (anrArray != null) {
for (String anr : anrArray) {
if (!TextUtils.isEmpty(anr)) { //Builder中新增聯絡人another電話
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Phone.RAW_CONTACT_ID, ref);
builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
builder.withValue(Phone.TYPE, Phone.TYPE_HOME);
builder.withValue(Phone.NUMBER, anr);
operationList.add(builder.build()); //Builder新增到operationList事務中
}
}
}
if (emailAddressArray != null) {
for (String emailAddress : emailAddressArray) {
if (!TextUtils.isEmpty(emailAddress)) { //Builder中新增聯絡人郵箱
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Email.RAW_CONTACT_ID, ref);
builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
builder.withValue(Email.TYPE, Email.TYPE_MOBILE);
builder.withValue(Email.ADDRESS, emailAddress);
operationList.add(builder.build()); //Builder新增到operationList事務中
}
}
}
if (BATCH_INSERT_NUMBER - operationList.size() < 10) {
try {
mContext.getContentResolver().applyBatch(
android.provider.ContactsContract.AUTHORITY,operationList);
} catch (Exception e) {
Log.e(TAG,String.format("%s: %s", e.toString(),e.getMessage()));
} finally {
operationList.clear();
}
}
}
至此,Import & Export 關於SIM卡的操作分析完成。