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