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