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.os.UserHandle; 37 import android.provider.ContactsContract; 38 import android.provider.ContactsContract.CommonDataKinds.Email; 39 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 40 import android.provider.ContactsContract.CommonDataKinds.Phone; 41 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 42 import android.provider.ContactsContract.Data; 43 import android.provider.ContactsContract.RawContacts; 44 import android.telecom.PhoneAccount; 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 (emailAddressArray != 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 getWindow().addSystemFlags( 236 android.view.WindowManager.LayoutParams 237 .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 238 239 Intent intent = getIntent(); 240 if (intent != null) { 241 final String accountName = intent.getStringExtra("account_name"); 242 final String accountType = intent.getStringExtra("account_type"); 243 if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) { 244 mAccount = new Account(accountName, accountType); 245 } 246 } 247 248 registerForContextMenu(getListView()); 249 250 ActionBar actionBar = getActionBar(); 251 if (actionBar != null) { 252 // android.R.id.home will be triggered in onOptionsItemSelected() 253 actionBar.setDisplayHomeAsUpEnabled(true); 254 } 255 } 256 257 @Override newAdapter()258 protected CursorAdapter newAdapter() { 259 return new SimpleCursorAdapter(this, R.layout.sim_import_list_entry, mCursor, 260 new String[] { "name" }, new int[] { android.R.id.text1 }); 261 } 262 263 @Override resolveIntent()264 protected Uri resolveIntent() { 265 final Intent intent = getIntent(); 266 int subId = -1; 267 if (intent.hasExtra("subscription_id")) { 268 subId = intent.getIntExtra("subscription_id", -1); 269 } 270 if (subId != -1) { 271 intent.setData(Uri.parse("content://icc/adn/subId/" + subId)); 272 } else { 273 intent.setData(Uri.parse("content://icc/adn")); 274 } 275 if (Intent.ACTION_PICK.equals(intent.getAction())) { 276 // "index" is 1-based 277 mInitialSelection = intent.getIntExtra("index", 0) - 1; 278 } 279 return intent.getData(); 280 } 281 282 @Override onCreateOptionsMenu(Menu menu)283 public boolean onCreateOptionsMenu(Menu menu) { 284 super.onCreateOptionsMenu(menu); 285 menu.add(0, MENU_IMPORT_ALL, 0, R.string.importAllSimEntries); 286 return true; 287 } 288 289 @Override onPrepareOptionsMenu(Menu menu)290 public boolean onPrepareOptionsMenu(Menu menu) { 291 MenuItem item = menu.findItem(MENU_IMPORT_ALL); 292 if (item != null) { 293 item.setVisible(mCursor != null && mCursor.getCount() > 0); 294 } 295 return super.onPrepareOptionsMenu(menu); 296 } 297 298 @Override onOptionsItemSelected(MenuItem item)299 public boolean onOptionsItemSelected(MenuItem item) { 300 switch (item.getItemId()) { 301 case android.R.id.home: 302 onBackPressed(); 303 return true; 304 case MENU_IMPORT_ALL: 305 CharSequence title = getString(R.string.importAllSimEntries); 306 CharSequence message = getString(R.string.importingSimContacts); 307 308 ImportAllSimContactsThread thread = new ImportAllSimContactsThread(); 309 310 // TODO: need to show some error dialog. 311 if (mCursor == null) { 312 Log.e(LOG_TAG, "cursor is null. Ignore silently."); 313 break; 314 } 315 mProgressDialog = new ProgressDialog(this); 316 mProgressDialog.setTitle(title); 317 mProgressDialog.setMessage(message); 318 mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 319 mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, 320 getString(R.string.cancel), thread); 321 mProgressDialog.setProgress(0); 322 mProgressDialog.setMax(mCursor.getCount()); 323 mProgressDialog.show(); 324 325 thread.start(); 326 327 return true; 328 } 329 return super.onOptionsItemSelected(item); 330 } 331 332 @Override onContextItemSelected(MenuItem item)333 public boolean onContextItemSelected(MenuItem item) { 334 switch (item.getItemId()) { 335 case MENU_IMPORT_ONE: 336 ContextMenu.ContextMenuInfo menuInfo = item.getMenuInfo(); 337 if (menuInfo instanceof AdapterView.AdapterContextMenuInfo) { 338 int position = ((AdapterView.AdapterContextMenuInfo)menuInfo).position; 339 importOneSimContact(position); 340 return true; 341 } 342 } 343 return super.onContextItemSelected(item); 344 } 345 346 @Override onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)347 public void onCreateContextMenu(ContextMenu menu, View v, 348 ContextMenu.ContextMenuInfo menuInfo) { 349 if (menuInfo instanceof AdapterView.AdapterContextMenuInfo) { 350 AdapterView.AdapterContextMenuInfo itemInfo = 351 (AdapterView.AdapterContextMenuInfo) menuInfo; 352 TextView textView = (TextView) itemInfo.targetView.findViewById(android.R.id.text1); 353 if (textView != null) { 354 menu.setHeaderTitle(textView.getText()); 355 } 356 menu.add(0, MENU_IMPORT_ONE, 0, R.string.importSimEntry); 357 } 358 } 359 360 @Override onListItemClick(ListView l, View v, int position, long id)361 public void onListItemClick(ListView l, View v, int position, long id) { 362 importOneSimContact(position); 363 } 364 365 @Override onKeyDown(int keyCode, KeyEvent event)366 public boolean onKeyDown(int keyCode, KeyEvent event) { 367 switch (keyCode) { 368 case KeyEvent.KEYCODE_CALL: { 369 if (mCursor != null && mCursor.moveToPosition(getSelectedItemPosition())) { 370 String phoneNumber = mCursor.getString(NUMBER_COLUMN); 371 if (phoneNumber == null || !TextUtils.isGraphic(phoneNumber)) { 372 // There is no number entered. 373 //TODO play error sound or something... 374 return true; 375 } 376 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 377 Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null)); 378 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 379 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 380 startActivityAsUser(intent, UserHandle.CURRENT); 381 finish(); 382 return true; 383 } 384 } 385 } 386 return super.onKeyDown(keyCode, event); 387 } 388 } 389