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