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