1 /* 2 * Copyright (C) 2007 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 com.android.phone; 18 19 import android.accounts.Account; 20 import android.app.ActionBar; 21 import android.app.ProgressDialog; 22 import android.content.ContentProviderOperation; 23 import android.content.ContentProviderResult; 24 import android.content.ContentResolver; 25 import android.content.ContentValues; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.content.DialogInterface.OnCancelListener; 29 import android.content.DialogInterface.OnClickListener; 30 import android.content.Intent; 31 import android.content.OperationApplicationException; 32 import android.database.Cursor; 33 import android.net.Uri; 34 import android.os.Bundle; 35 import android.os.RemoteException; 36 import android.provider.ContactsContract; 37 import android.provider.ContactsContract.CommonDataKinds.Email; 38 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 39 import android.provider.ContactsContract.CommonDataKinds.Phone; 40 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 41 import android.provider.ContactsContract.Data; 42 import android.provider.ContactsContract.RawContacts; 43 import android.telecom.PhoneAccount; 44 import android.telephony.SubscriptionManager; 45 import android.text.TextUtils; 46 import android.util.Log; 47 import android.view.ContextMenu; 48 import android.view.KeyEvent; 49 import android.view.Menu; 50 import android.view.MenuItem; 51 import android.view.View; 52 import android.widget.AdapterView; 53 import android.widget.CursorAdapter; 54 import android.widget.ListView; 55 import android.widget.SimpleCursorAdapter; 56 import android.widget.TextView; 57 import android.widget.Toast; 58 59 import java.util.ArrayList; 60 61 /** 62 * SIM Address Book UI for the Phone app. 63 */ 64 public class SimContacts extends ADNList { 65 private static final String LOG_TAG = "SimContacts"; 66 67 static final ContentValues sEmptyContentValues = new ContentValues(); 68 69 private static final int MENU_IMPORT_ONE = 1; 70 private static final int MENU_IMPORT_ALL = 2; 71 private ProgressDialog mProgressDialog; 72 73 private Account mAccount; 74 75 private static class NamePhoneTypePair { 76 final String name; 77 final int phoneType; NamePhoneTypePair(String nameWithPhoneType)78 public NamePhoneTypePair(String nameWithPhoneType) { 79 // Look for /W /H /M or /O at the end of the name signifying the type 80 int nameLen = nameWithPhoneType.length(); 81 if (nameLen - 2 >= 0 && nameWithPhoneType.charAt(nameLen - 2) == '/') { 82 char c = Character.toUpperCase(nameWithPhoneType.charAt(nameLen - 1)); 83 if (c == 'W') { 84 phoneType = Phone.TYPE_WORK; 85 } else if (c == 'M' || c == 'O') { 86 phoneType = Phone.TYPE_MOBILE; 87 } else if (c == 'H') { 88 phoneType = Phone.TYPE_HOME; 89 } else { 90 phoneType = Phone.TYPE_OTHER; 91 } 92 name = nameWithPhoneType.substring(0, nameLen - 2); 93 } else { 94 phoneType = Phone.TYPE_OTHER; 95 name = nameWithPhoneType; 96 } 97 } 98 } 99 100 private class ImportAllSimContactsThread extends Thread 101 implements OnCancelListener, OnClickListener { 102 103 boolean mCanceled = false; 104 ImportAllSimContactsThread()105 public ImportAllSimContactsThread() { 106 super("ImportAllSimContactsThread"); 107 } 108 109 @Override run()110 public void run() { 111 final ContentValues emptyContentValues = new ContentValues(); 112 final ContentResolver resolver = getContentResolver(); 113 114 mCursor.moveToPosition(-1); 115 while (!mCanceled && mCursor.moveToNext()) { 116 actuallyImportOneSimContact(mCursor, resolver, mAccount); 117 mProgressDialog.incrementProgressBy(1); 118 } 119 120 mProgressDialog.dismiss(); 121 finish(); 122 } 123 onCancel(DialogInterface dialog)124 public void onCancel(DialogInterface dialog) { 125 mCanceled = true; 126 } 127 onClick(DialogInterface dialog, int which)128 public void onClick(DialogInterface dialog, int which) { 129 if (which == DialogInterface.BUTTON_NEGATIVE) { 130 mCanceled = true; 131 mProgressDialog.dismiss(); 132 } else { 133 Log.e(LOG_TAG, "Unknown button event has come: " + dialog.toString()); 134 } 135 } 136 } 137 actuallyImportOneSimContact( final Cursor cursor, final ContentResolver resolver, Account account)138 private static boolean actuallyImportOneSimContact( 139 final Cursor cursor, final ContentResolver resolver, Account account) { 140 final NamePhoneTypePair namePhoneTypePair = 141 new NamePhoneTypePair(cursor.getString(NAME_COLUMN)); 142 final String name = namePhoneTypePair.name; 143 final int phoneType = namePhoneTypePair.phoneType; 144 final String phoneNumber = cursor.getString(NUMBER_COLUMN); 145 final String emailAddresses = cursor.getString(EMAILS_COLUMN); 146 final String[] emailAddressArray; 147 if (!TextUtils.isEmpty(emailAddresses)) { 148 emailAddressArray = emailAddresses.split(","); 149 } else { 150 emailAddressArray = null; 151 } 152 153 final ArrayList<ContentProviderOperation> operationList = 154 new ArrayList<ContentProviderOperation>(); 155 ContentProviderOperation.Builder builder = 156 ContentProviderOperation.newInsert(RawContacts.CONTENT_URI); 157 String myGroupsId = null; 158 if (account != null) { 159 builder.withValue(RawContacts.ACCOUNT_NAME, account.name); 160 builder.withValue(RawContacts.ACCOUNT_TYPE, account.type); 161 } else { 162 builder.withValues(sEmptyContentValues); 163 } 164 operationList.add(builder.build()); 165 166 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 167 builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0); 168 builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); 169 builder.withValue(StructuredName.DISPLAY_NAME, name); 170 operationList.add(builder.build()); 171 172 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 173 builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0); 174 builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 175 builder.withValue(Phone.TYPE, phoneType); 176 builder.withValue(Phone.NUMBER, phoneNumber); 177 builder.withValue(Data.IS_PRIMARY, 1); 178 operationList.add(builder.build()); 179 180 if (emailAddresses != null) { 181 for (String emailAddress : emailAddressArray) { 182 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 183 builder.withValueBackReference(Email.RAW_CONTACT_ID, 0); 184 builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); 185 builder.withValue(Email.TYPE, Email.TYPE_MOBILE); 186 builder.withValue(Email.DATA, emailAddress); 187 operationList.add(builder.build()); 188 } 189 } 190 191 if (myGroupsId != null) { 192 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); 193 builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0); 194 builder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 195 builder.withValue(GroupMembership.GROUP_SOURCE_ID, myGroupsId); 196 operationList.add(builder.build()); 197 } 198 199 try { 200 final ContentProviderResult[] results = resolver.applyBatch(ContactsContract.AUTHORITY, 201 operationList); 202 return results.length > 0; // Batch operations either all succeed or all fail. 203 } catch (RemoteException e) { 204 Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage())); 205 } catch (OperationApplicationException e) { 206 Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage())); 207 } 208 return false; 209 } 210 importOneSimContact(int position)211 private void importOneSimContact(int position) { 212 final ContentResolver resolver = getContentResolver(); 213 final Context context = getApplicationContext(); 214 if (mCursor.moveToPosition(position)) { 215 if (actuallyImportOneSimContact(mCursor, resolver, mAccount)){ 216 Toast.makeText(context, R.string.singleContactImportedMsg, Toast.LENGTH_SHORT) 217 .show(); 218 } else { 219 Toast.makeText(context, R.string.failedToImportSingleContactMsg, Toast.LENGTH_SHORT) 220 .show(); 221 } 222 } else { 223 Log.e(LOG_TAG, "Failed to move the cursor to the position \"" + position + "\""); 224 Toast.makeText(context, R.string.failedToImportSingleContactMsg, Toast.LENGTH_SHORT) 225 .show(); 226 } 227 } 228 229 /* Followings are overridden methods */ 230 231 @Override onCreate(Bundle icicle)232 protected void onCreate(Bundle icicle) { 233 super.onCreate(icicle); 234 235 Intent intent = getIntent(); 236 if (intent != null) { 237 final String accountName = intent.getStringExtra("account_name"); 238 final String accountType = intent.getStringExtra("account_type"); 239 if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) { 240 mAccount = new Account(accountName, accountType); 241 } 242 } 243 244 registerForContextMenu(getListView()); 245 246 ActionBar actionBar = getActionBar(); 247 if (actionBar != null) { 248 // android.R.id.home will be triggered in onOptionsItemSelected() 249 actionBar.setDisplayHomeAsUpEnabled(true); 250 } 251 } 252 253 @Override newAdapter()254 protected CursorAdapter newAdapter() { 255 return new SimpleCursorAdapter(this, R.layout.sim_import_list_entry, mCursor, 256 new String[] { "name" }, new int[] { android.R.id.text1 }); 257 } 258 259 @Override resolveIntent()260 protected Uri resolveIntent() { 261 final Intent intent = getIntent(); 262 int subId = -1; 263 if (intent.hasExtra("subscription_id")) { 264 subId = intent.getIntExtra("subscription_id", -1); 265 } 266 if (subId != -1) { 267 intent.setData(Uri.parse("content://icc/adn/subId/" + subId)); 268 } else { 269 intent.setData(Uri.parse("content://icc/adn")); 270 } 271 if (Intent.ACTION_PICK.equals(intent.getAction())) { 272 // "index" is 1-based 273 mInitialSelection = intent.getIntExtra("index", 0) - 1; 274 } 275 return intent.getData(); 276 } 277 278 @Override onCreateOptionsMenu(Menu menu)279 public boolean onCreateOptionsMenu(Menu menu) { 280 super.onCreateOptionsMenu(menu); 281 menu.add(0, MENU_IMPORT_ALL, 0, R.string.importAllSimEntries); 282 return true; 283 } 284 285 @Override onPrepareOptionsMenu(Menu menu)286 public boolean onPrepareOptionsMenu(Menu menu) { 287 MenuItem item = menu.findItem(MENU_IMPORT_ALL); 288 if (item != null) { 289 item.setVisible(mCursor != null && mCursor.getCount() > 0); 290 } 291 return super.onPrepareOptionsMenu(menu); 292 } 293 294 @Override onOptionsItemSelected(MenuItem item)295 public boolean onOptionsItemSelected(MenuItem item) { 296 switch (item.getItemId()) { 297 case android.R.id.home: 298 onBackPressed(); 299 return true; 300 case MENU_IMPORT_ALL: 301 CharSequence title = getString(R.string.importAllSimEntries); 302 CharSequence message = getString(R.string.importingSimContacts); 303 304 ImportAllSimContactsThread thread = new ImportAllSimContactsThread(); 305 306 // TODO: need to show some error dialog. 307 if (mCursor == null) { 308 Log.e(LOG_TAG, "cursor is null. Ignore silently."); 309 break; 310 } 311 mProgressDialog = new ProgressDialog(this); 312 mProgressDialog.setTitle(title); 313 mProgressDialog.setMessage(message); 314 mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 315 mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, 316 getString(R.string.cancel), thread); 317 mProgressDialog.setProgress(0); 318 mProgressDialog.setMax(mCursor.getCount()); 319 mProgressDialog.show(); 320 321 thread.start(); 322 323 return true; 324 } 325 return super.onOptionsItemSelected(item); 326 } 327 328 @Override onContextItemSelected(MenuItem item)329 public boolean onContextItemSelected(MenuItem item) { 330 switch (item.getItemId()) { 331 case MENU_IMPORT_ONE: 332 ContextMenu.ContextMenuInfo menuInfo = item.getMenuInfo(); 333 if (menuInfo instanceof AdapterView.AdapterContextMenuInfo) { 334 int position = ((AdapterView.AdapterContextMenuInfo)menuInfo).position; 335 importOneSimContact(position); 336 return true; 337 } 338 } 339 return super.onContextItemSelected(item); 340 } 341 342 @Override onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)343 public void onCreateContextMenu(ContextMenu menu, View v, 344 ContextMenu.ContextMenuInfo menuInfo) { 345 if (menuInfo instanceof AdapterView.AdapterContextMenuInfo) { 346 AdapterView.AdapterContextMenuInfo itemInfo = 347 (AdapterView.AdapterContextMenuInfo) menuInfo; 348 TextView textView = (TextView) itemInfo.targetView.findViewById(android.R.id.text1); 349 if (textView != null) { 350 menu.setHeaderTitle(textView.getText()); 351 } 352 menu.add(0, MENU_IMPORT_ONE, 0, R.string.importSimEntry); 353 } 354 } 355 356 @Override onListItemClick(ListView l, View v, int position, long id)357 public void onListItemClick(ListView l, View v, int position, long id) { 358 importOneSimContact(position); 359 } 360 361 @Override onKeyDown(int keyCode, KeyEvent event)362 public boolean onKeyDown(int keyCode, KeyEvent event) { 363 switch (keyCode) { 364 case KeyEvent.KEYCODE_CALL: { 365 if (mCursor != null && mCursor.moveToPosition(getSelectedItemPosition())) { 366 String phoneNumber = mCursor.getString(NUMBER_COLUMN); 367 if (phoneNumber == null || !TextUtils.isGraphic(phoneNumber)) { 368 // There is no number entered. 369 //TODO play error sound or something... 370 return true; 371 } 372 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 373 Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null)); 374 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 375 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 376 startActivity(intent); 377 finish(); 378 return true; 379 } 380 } 381 } 382 return super.onKeyDown(keyCode, event); 383 } 384 } 385