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.setKeyListener(DialerKeyListener.getInstance()); 247 mNumberField.setOnFocusChangeListener(mOnFocusChangeHandler); 248 mNumberField.setOnClickListener(mClicked); 249 } 250 251 if (!mAddContact) { 252 if (mNameField != null) { 253 mNameField.setText(mName); 254 } 255 if (mNumberField != null) { 256 mNumberField.setText(mNumber); 257 } 258 } 259 260 mButton = (Button) findViewById(R.id.button); 261 if (mButton != null) { 262 mButton.setOnClickListener(mClicked); 263 } 264 265 mPinFieldContainer = (LinearLayout) findViewById(R.id.pinc); 266 267 } 268 getNameFromTextField()269 private String getNameFromTextField() { 270 return mNameField.getText().toString(); 271 } 272 getNumberFromTextField()273 private String getNumberFromTextField() { 274 return mNumberField.getText().toString(); 275 } 276 277 /** 278 * @param number is voice mail number 279 * @return true if number length is less than 20-digit limit 280 * 281 * TODO: Fix this logic. 282 */ isValidNumber(String number)283 private boolean isValidNumber(String number) { 284 return (number.length() <= 20); 285 } 286 287 addContact()288 private void addContact() { 289 if (DBG) log("addContact"); 290 291 final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField()); 292 293 if (!isValidNumber(number)) { 294 handleResult(false, true); 295 return; 296 } 297 298 Uri uri = FdnList.getContentUri(mSubscriptionInfoHelper); 299 300 ContentValues bundle = new ContentValues(3); 301 bundle.put("tag", getNameFromTextField()); 302 bundle.put("number", number); 303 bundle.put("pin2", mPin2); 304 305 mQueryHandler = new QueryHandler(getContentResolver()); 306 mQueryHandler.startInsert(0, null, uri, bundle); 307 displayProgress(true); 308 showStatus(getResources().getText(R.string.adding_fdn_contact)); 309 } 310 updateContact()311 private void updateContact() { 312 if (DBG) log("updateContact"); 313 314 final String name = getNameFromTextField(); 315 final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField()); 316 317 if (!isValidNumber(number)) { 318 handleResult(false, true); 319 return; 320 } 321 Uri uri = FdnList.getContentUri(mSubscriptionInfoHelper); 322 323 ContentValues bundle = new ContentValues(); 324 bundle.put("tag", mName); 325 bundle.put("number", mNumber); 326 bundle.put("newTag", name); 327 bundle.put("newNumber", number); 328 bundle.put("pin2", mPin2); 329 330 mQueryHandler = new QueryHandler(getContentResolver()); 331 mQueryHandler.startUpdate(0, null, uri, bundle, null, null); 332 displayProgress(true); 333 showStatus(getResources().getText(R.string.updating_fdn_contact)); 334 } 335 336 /** 337 * Handle the delete command, based upon the state of the Activity. 338 */ deleteSelected()339 private void deleteSelected() { 340 // delete ONLY if this is NOT a new contact. 341 if (!mAddContact) { 342 Intent intent = mSubscriptionInfoHelper.getIntent(DeleteFdnContactScreen.class); 343 intent.putExtra(INTENT_EXTRA_NAME, mName); 344 intent.putExtra(INTENT_EXTRA_NUMBER, mNumber); 345 startActivity(intent); 346 } 347 finish(); 348 } 349 authenticatePin2()350 private void authenticatePin2() { 351 Intent intent = new Intent(); 352 intent.setClass(this, GetPin2Screen.class); 353 intent.setData(FdnList.getContentUri(mSubscriptionInfoHelper)); 354 startActivityForResult(intent, PIN2_REQUEST_CODE); 355 } 356 displayProgress(boolean flag)357 private void displayProgress(boolean flag) { 358 // indicate we are busy. 359 mDataBusy = flag; 360 getWindow().setFeatureInt( 361 Window.FEATURE_INDETERMINATE_PROGRESS, 362 mDataBusy ? PROGRESS_VISIBILITY_ON : PROGRESS_VISIBILITY_OFF); 363 // make sure we don't allow calls to save when we're 364 // not ready for them. 365 mButton.setClickable(!mDataBusy); 366 } 367 368 /** 369 * Removed the status field, with preference to displaying a toast 370 * to match the rest of settings UI. 371 */ showStatus(CharSequence statusMsg)372 private void showStatus(CharSequence statusMsg) { 373 if (statusMsg != null) { 374 Toast.makeText(this, statusMsg, Toast.LENGTH_LONG) 375 .show(); 376 } 377 } 378 handleResult(boolean success, boolean invalidNumber)379 private void handleResult(boolean success, boolean invalidNumber) { 380 if (success) { 381 if (DBG) log("handleResult: success!"); 382 showStatus(getResources().getText(mAddContact ? 383 R.string.fdn_contact_added : R.string.fdn_contact_updated)); 384 } else { 385 if (DBG) log("handleResult: failed!"); 386 if (invalidNumber) { 387 showStatus(getResources().getText(R.string.fdn_invalid_number)); 388 } else { 389 if (PhoneFactory.getDefaultPhone().getIccCard().getIccPin2Blocked()) { 390 showStatus(getResources().getText(R.string.fdn_enable_puk2_requested)); 391 } else if (PhoneFactory.getDefaultPhone().getIccCard().getIccPuk2Blocked()) { 392 showStatus(getResources().getText(R.string.puk2_blocked)); 393 } else { 394 // There's no way to know whether the failure is due to incorrect PIN2 or 395 // an inappropriate phone number. 396 showStatus(getResources().getText(R.string.pin2_or_fdn_invalid)); 397 } 398 } 399 } 400 401 mHandler.postDelayed(new Runnable() { 402 @Override 403 public void run() { 404 finish(); 405 } 406 }, 2000); 407 408 } 409 410 private final View.OnClickListener mClicked = new View.OnClickListener() { 411 @Override 412 public void onClick(View v) { 413 if (mPinFieldContainer.getVisibility() != View.VISIBLE) { 414 return; 415 } 416 417 if (v == mNameField) { 418 mNumberField.requestFocus(); 419 } else if (v == mNumberField) { 420 mButton.requestFocus(); 421 } else if (v == mButton) { 422 // Authenticate the pin AFTER the contact information 423 // is entered, and if we're not busy. 424 if (!mDataBusy) { 425 authenticatePin2(); 426 } 427 } 428 } 429 }; 430 431 private final View.OnFocusChangeListener mOnFocusChangeHandler = 432 new View.OnFocusChangeListener() { 433 @Override 434 public void onFocusChange(View v, boolean hasFocus) { 435 if (hasFocus) { 436 TextView textView = (TextView) v; 437 Selection.selectAll((Spannable) textView.getText()); 438 } 439 } 440 }; 441 442 private class QueryHandler extends AsyncQueryHandler { QueryHandler(ContentResolver cr)443 public QueryHandler(ContentResolver cr) { 444 super(cr); 445 } 446 447 @Override onQueryComplete(int token, Object cookie, Cursor c)448 protected void onQueryComplete(int token, Object cookie, Cursor c) { 449 } 450 451 @Override onInsertComplete(int token, Object cookie, Uri uri)452 protected void onInsertComplete(int token, Object cookie, Uri uri) { 453 if (DBG) log("onInsertComplete"); 454 displayProgress(false); 455 handleResult(uri != null, false); 456 } 457 458 @Override onUpdateComplete(int token, Object cookie, int result)459 protected void onUpdateComplete(int token, Object cookie, int result) { 460 if (DBG) log("onUpdateComplete"); 461 displayProgress(false); 462 handleResult(result > 0, false); 463 } 464 465 @Override onDeleteComplete(int token, Object cookie, int result)466 protected void onDeleteComplete(int token, Object cookie, int result) { 467 } 468 } 469 log(String msg)470 private void log(String msg) { 471 Log.d(LOG_TAG, "[EditFdnContact] " + msg); 472 } 473 } 474