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