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