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