1 /* 2 * Copyright (C) 2006 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.settings.fdn; 18 19 import static android.view.Window.PROGRESS_VISIBILITY_OFF; 20 import static android.view.Window.PROGRESS_VISIBILITY_ON; 21 22 import android.app.Activity; 23 import android.content.AsyncQueryHandler; 24 import android.content.ContentResolver; 25 import android.content.ContentValues; 26 import android.content.Intent; 27 import android.content.res.Resources; 28 import android.database.Cursor; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.provider.Contacts.PeopleColumns; 33 import android.provider.Contacts.PhonesColumns; 34 import android.provider.ContactsContract.CommonDataKinds; 35 import android.telephony.PhoneNumberUtils; 36 import android.text.Editable; 37 import android.text.Selection; 38 import android.text.Spannable; 39 import android.text.TextUtils; 40 import android.text.TextWatcher; 41 import android.text.method.DialerKeyListener; 42 import android.util.Log; 43 import android.view.Menu; 44 import android.view.MenuItem; 45 import android.view.View; 46 import android.view.Window; 47 import android.widget.Button; 48 import android.widget.EditText; 49 import android.widget.LinearLayout; 50 import android.widget.TextView; 51 import android.widget.Toast; 52 53 import com.android.phone.PhoneGlobals; 54 import com.android.phone.R; 55 import com.android.phone.SubscriptionInfoHelper; 56 import com.android.internal.telephony.PhoneFactory; 57 58 /** 59 * Activity to let the user add or edit an FDN contact. 60 */ 61 public class EditFdnContactScreen extends Activity { 62 private static final String LOG_TAG = PhoneGlobals.LOG_TAG; 63 private static final boolean DBG = false; 64 65 // Menu item codes 66 private static final int MENU_IMPORT = 1; 67 private static final int MENU_DELETE = 2; 68 69 private static final String INTENT_EXTRA_NAME = "name"; 70 private static final String INTENT_EXTRA_NUMBER = "number"; 71 72 private static final int PIN2_REQUEST_CODE = 100; 73 74 private SubscriptionInfoHelper mSubscriptionInfoHelper; 75 76 private String mName; 77 private String mNumber; 78 private String mPin2; 79 private boolean mAddContact; 80 private QueryHandler mQueryHandler; 81 82 private EditText mNameField; 83 private EditText mNumberField; 84 private LinearLayout mPinFieldContainer; 85 private Button mButton; 86 87 private Handler mHandler = new Handler(); 88 89 /** 90 * Constants used in importing from contacts 91 */ 92 /** request code when invoking subactivity */ 93 private static final int CONTACTS_PICKER_CODE = 200; 94 /** projection for phone number query */ 95 private static final String[] NUM_PROJECTION = new String[] {CommonDataKinds.Phone.DISPLAY_NAME, 96 CommonDataKinds.Phone.NUMBER}; 97 /** static intent to invoke phone number picker */ 98 private static final Intent CONTACT_IMPORT_INTENT; 99 static { 100 CONTACT_IMPORT_INTENT = new Intent(Intent.ACTION_GET_CONTENT); 101 CONTACT_IMPORT_INTENT.setType(CommonDataKinds.Phone.CONTENT_ITEM_TYPE); 102 } 103 /** flag to track saving state */ 104 private boolean mDataBusy; 105 106 @Override 107 protected void onCreate(Bundle icicle) { 108 super.onCreate(icicle); 109 110 resolveIntent(); 111 112 getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 113 setContentView(R.layout.edit_fdn_contact_screen); 114 setupView(); 115 setTitle(mAddContact ? R.string.add_fdn_contact : R.string.edit_fdn_contact); 116 117 displayProgress(false); 118 } 119 120 /** 121 * We now want to bring up the pin request screen AFTER the 122 * contact information is displayed, to help with user 123 * experience. 124 * 125 * Also, process the results from the contact picker. 126 */ 127 @Override 128 protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 129 if (DBG) log("onActivityResult request:" + requestCode + " result:" + resultCode); 130 131 switch (requestCode) { 132 case PIN2_REQUEST_CODE: 133 Bundle extras = (intent != null) ? intent.getExtras() : null; 134 if (extras != null) { 135 mPin2 = extras.getString("pin2"); 136 if (mAddContact) { 137 addContact(); 138 } else { 139 updateContact(); 140 } 141 } else if (resultCode != RESULT_OK) { 142 // if they cancelled, then we just cancel too. 143 if (DBG) log("onActivityResult: cancelled."); 144 finish(); 145 } 146 break; 147 148 // look for the data associated with this number, and update 149 // the display with it. 150 case CONTACTS_PICKER_CODE: 151 if (resultCode != RESULT_OK) { 152 if (DBG) log("onActivityResult: cancelled."); 153 return; 154 } 155 Cursor cursor = null; 156 try { 157 cursor = getContentResolver().query(intent.getData(), 158 NUM_PROJECTION, null, null, null); 159 if ((cursor == null) || (!cursor.moveToFirst())) { 160 Log.w(LOG_TAG,"onActivityResult: bad contact data, no results found."); 161 return; 162 } 163 mNameField.setText(cursor.getString(0)); 164 mNumberField.setText(cursor.getString(1)); 165 } finally { 166 if (cursor != null) { 167 cursor.close(); 168 } 169 } 170 break; 171 } 172 } 173 174 /** 175 * Overridden to display the import and delete commands. 176 */ 177 @Override 178 public boolean onCreateOptionsMenu(Menu menu) { 179 super.onCreateOptionsMenu(menu); 180 181 Resources r = getResources(); 182 183 // Added the icons to the context menu 184 menu.add(0, MENU_IMPORT, 0, r.getString(R.string.importToFDNfromContacts)) 185 .setIcon(R.drawable.ic_menu_contact); 186 menu.add(0, MENU_DELETE, 0, r.getString(R.string.menu_delete)) 187 .setIcon(android.R.drawable.ic_menu_delete); 188 return true; 189 } 190 191 /** 192 * Allow the menu to be opened ONLY if we're not busy. 193 */ 194 @Override 195 public boolean onPrepareOptionsMenu(Menu menu) { 196 boolean result = super.onPrepareOptionsMenu(menu); 197 return mDataBusy ? false : result; 198 } 199 200 /** 201 * Overridden to allow for handling of delete and import. 202 */ 203 @Override 204 public boolean onOptionsItemSelected(MenuItem item) { 205 switch (item.getItemId()) { 206 case MENU_IMPORT: 207 startActivityForResult(CONTACT_IMPORT_INTENT, CONTACTS_PICKER_CODE); 208 return true; 209 210 case MENU_DELETE: 211 deleteSelected(); 212 return true; 213 214 case android.R.id.home: 215 onBackPressed(); 216 return true; 217 } 218 219 return super.onOptionsItemSelected(item); 220 } 221 222 private void resolveIntent() { 223 Intent intent = getIntent(); 224 225 mSubscriptionInfoHelper = new SubscriptionInfoHelper(this, intent); 226 227 mName = intent.getStringExtra(INTENT_EXTRA_NAME); 228 mNumber = intent.getStringExtra(INTENT_EXTRA_NUMBER); 229 230 mAddContact = TextUtils.isEmpty(mNumber); 231 } 232 233 /** 234 * We have multiple layouts, one to indicate that the user needs to 235 * open the keyboard to enter information (if the keybord is hidden). 236 * So, we need to make sure that the layout here matches that in the 237 * layout file. 238 */ 239 private void setupView() { 240 mNameField = (EditText) findViewById(R.id.fdn_name); 241 if (mNameField != null) { 242 mNameField.setOnFocusChangeListener(mOnFocusChangeHandler); 243 mNameField.setOnClickListener(mClicked); 244 mNameField.addTextChangedListener(mTextWatcher); 245 } 246 247 mNumberField = (EditText) findViewById(R.id.fdn_number); 248 if (mNumberField != null) { 249 mNumberField.setTextDirection(View.TEXT_DIRECTION_LTR); 250 mNumberField.setKeyListener(DialerKeyListener.getInstance()); 251 mNumberField.setOnFocusChangeListener(mOnFocusChangeHandler); 252 mNumberField.setOnClickListener(mClicked); 253 mNumberField.addTextChangedListener(mTextWatcher); 254 } 255 256 if (!mAddContact) { 257 if (mNameField != null) { 258 mNameField.setText(mName); 259 } 260 if (mNumberField != null) { 261 mNumberField.setText(mNumber); 262 } 263 } 264 265 mButton = (Button) findViewById(R.id.button); 266 if (mButton != null) { 267 mButton.setOnClickListener(mClicked); 268 setButtonEnabled(); 269 } 270 271 mPinFieldContainer = (LinearLayout) findViewById(R.id.pinc); 272 273 } 274 275 private String getNameFromTextField() { 276 return mNameField.getText().toString(); 277 } 278 279 private String getNumberFromTextField() { 280 return mNumberField.getText().toString(); 281 } 282 283 /** 284 * Enable Save button if text has been added to both name and number 285 */ 286 private void setButtonEnabled() { 287 if (mButton != null && mNameField != null && mNumberField != null) { 288 mButton.setEnabled(mNameField.length() > 0 && mNumberField.length() > 0); 289 } 290 } 291 292 /** 293 * @param number is voice mail number 294 * @return true if number length is less than 20-digit limit 295 * 296 * TODO: Fix this logic. 297 */ 298 private boolean isValidNumber(String number) { 299 return (number.length() <= 20) && (number.length() > 0); 300 } 301 302 303 private void addContact() { 304 if (DBG) log("addContact"); 305 306 final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField()); 307 308 if (!isValidNumber(number)) { 309 handleResult(false, true); 310 return; 311 } 312 313 Uri uri = FdnList.getContentUri(mSubscriptionInfoHelper); 314 315 ContentValues bundle = new ContentValues(3); 316 bundle.put("tag", getNameFromTextField()); 317 bundle.put("number", number); 318 bundle.put("pin2", mPin2); 319 320 mQueryHandler = new QueryHandler(getContentResolver()); 321 mQueryHandler.startInsert(0, null, uri, bundle); 322 displayProgress(true); 323 showStatus(getResources().getText(R.string.adding_fdn_contact)); 324 } 325 326 private void updateContact() { 327 if (DBG) log("updateContact"); 328 329 final String name = getNameFromTextField(); 330 final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField()); 331 332 if (!isValidNumber(number)) { 333 handleResult(false, true); 334 return; 335 } 336 Uri uri = FdnList.getContentUri(mSubscriptionInfoHelper); 337 338 ContentValues bundle = new ContentValues(); 339 bundle.put("tag", mName); 340 bundle.put("number", mNumber); 341 bundle.put("newTag", name); 342 bundle.put("newNumber", number); 343 bundle.put("pin2", mPin2); 344 345 mQueryHandler = new QueryHandler(getContentResolver()); 346 mQueryHandler.startUpdate(0, null, uri, bundle, null, null); 347 displayProgress(true); 348 showStatus(getResources().getText(R.string.updating_fdn_contact)); 349 } 350 351 /** 352 * Handle the delete command, based upon the state of the Activity. 353 */ 354 private void deleteSelected() { 355 // delete ONLY if this is NOT a new contact. 356 if (!mAddContact) { 357 Intent intent = mSubscriptionInfoHelper.getIntent(DeleteFdnContactScreen.class); 358 intent.putExtra(INTENT_EXTRA_NAME, mName); 359 intent.putExtra(INTENT_EXTRA_NUMBER, mNumber); 360 startActivity(intent); 361 } 362 finish(); 363 } 364 365 private void authenticatePin2() { 366 Intent intent = new Intent(); 367 intent.setClass(this, GetPin2Screen.class); 368 intent.setData(FdnList.getContentUri(mSubscriptionInfoHelper)); 369 startActivityForResult(intent, PIN2_REQUEST_CODE); 370 } 371 372 private void displayProgress(boolean flag) { 373 // indicate we are busy. 374 mDataBusy = flag; 375 getWindow().setFeatureInt( 376 Window.FEATURE_INDETERMINATE_PROGRESS, 377 mDataBusy ? PROGRESS_VISIBILITY_ON : PROGRESS_VISIBILITY_OFF); 378 // make sure we don't allow calls to save when we're 379 // not ready for them. 380 mButton.setClickable(!mDataBusy); 381 } 382 383 /** 384 * Removed the status field, with preference to displaying a toast 385 * to match the rest of settings UI. 386 */ 387 private void showStatus(CharSequence statusMsg) { 388 if (statusMsg != null) { 389 Toast.makeText(this, statusMsg, Toast.LENGTH_LONG) 390 .show(); 391 } 392 } 393 394 private void handleResult(boolean success, boolean invalidNumber) { 395 if (success) { 396 if (DBG) log("handleResult: success!"); 397 showStatus(getResources().getText(mAddContact ? 398 R.string.fdn_contact_added : R.string.fdn_contact_updated)); 399 } else { 400 if (DBG) log("handleResult: failed!"); 401 if (invalidNumber) { 402 showStatus(getResources().getText(R.string.fdn_invalid_number)); 403 } else { 404 if (PhoneFactory.getDefaultPhone().getIccCard().getIccPin2Blocked()) { 405 showStatus(getResources().getText(R.string.fdn_enable_puk2_requested)); 406 } else if (PhoneFactory.getDefaultPhone().getIccCard().getIccPuk2Blocked()) { 407 showStatus(getResources().getText(R.string.puk2_blocked)); 408 } else { 409 // There's no way to know whether the failure is due to incorrect PIN2 or 410 // an inappropriate phone number. 411 showStatus(getResources().getText(R.string.pin2_or_fdn_invalid)); 412 } 413 } 414 } 415 416 mHandler.postDelayed(new Runnable() { 417 @Override 418 public void run() { 419 finish(); 420 } 421 }, 2000); 422 423 } 424 425 private final View.OnClickListener mClicked = new View.OnClickListener() { 426 @Override 427 public void onClick(View v) { 428 if (mPinFieldContainer.getVisibility() != View.VISIBLE) { 429 return; 430 } 431 432 if (v == mNameField) { 433 mNumberField.requestFocus(); 434 } else if (v == mNumberField) { 435 mButton.requestFocus(); 436 } else if (v == mButton) { 437 final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField()); 438 439 if (!isValidNumber(number)) { 440 handleResult(false, true); 441 return; 442 } 443 // Authenticate the pin AFTER the contact information 444 // is entered, and if we're not busy. 445 if (!mDataBusy) { 446 authenticatePin2(); 447 } 448 } 449 } 450 }; 451 452 private final View.OnFocusChangeListener mOnFocusChangeHandler = 453 new View.OnFocusChangeListener() { 454 @Override 455 public void onFocusChange(View v, boolean hasFocus) { 456 if (hasFocus) { 457 TextView textView = (TextView) v; 458 Selection.selectAll((Spannable) textView.getText()); 459 } 460 } 461 }; 462 463 private final TextWatcher mTextWatcher = new TextWatcher() { 464 @Override 465 public void afterTextChanged(Editable arg0) {} 466 467 @Override 468 public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {} 469 470 @Override 471 public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { 472 setButtonEnabled(); 473 } 474 }; 475 476 private class QueryHandler extends AsyncQueryHandler { 477 public QueryHandler(ContentResolver cr) { 478 super(cr); 479 } 480 481 @Override 482 protected void onQueryComplete(int token, Object cookie, Cursor c) { 483 } 484 485 @Override 486 protected void onInsertComplete(int token, Object cookie, Uri uri) { 487 if (DBG) log("onInsertComplete"); 488 displayProgress(false); 489 handleResult(uri != null, false); 490 } 491 492 @Override 493 protected void onUpdateComplete(int token, Object cookie, int result) { 494 if (DBG) log("onUpdateComplete"); 495 displayProgress(false); 496 handleResult(result > 0, false); 497 } 498 499 @Override 500 protected void onDeleteComplete(int token, Object cookie, int result) { 501 } 502 } 503 504 private void log(String msg) { 505 Log.d(LOG_TAG, "[EditFdnContact] " + msg); 506 } 507 } 508