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