@VisibleForTesting static boolean isNumberAddress(String number) { if (number.contains("@")) return false; if (GroupUtil.isEncodedGroup(number)) return false; final String networkNumber = PhoneNumberUtils.extractNetworkPortion(number); if (TextUtils.isEmpty(networkNumber)) return false; if (networkNumber.length() < 3) return false; return PhoneNumberUtils.isWellFormedSmsAddress(number); }
// Some received sms's have addresses such as "OakfieldCPS" or "T-Mobile". This // function will attempt to identify these and return true. If the number contains // 3 or more digits, such as "jello123", this function will return false. // Some countries have 3 digits shortcodes and we have to identify them as numbers. // http://en.wikipedia.org/wiki/Short_code // Examples of input/output for this function: // "Jello123" -> false [3 digits, it is considered to be the phone number "123"] // "T-Mobile" -> true [it is considered to be the address "T-Mobile"] // "Mobile1" -> true [1 digit, it is considered to be the address "Mobile1"] // "Dogs77" -> true [2 digits, it is considered to be the address "Dogs77"] // "****1" -> true [1 digits, it is considered to be the address "****1"] // "#4#5#6#" -> true [it is considered to be the address "#4#5#6#"] // "AB12" -> true [2 digits, it is considered to be the address "AB12"] // "12" -> true [2 digits, it is considered to be the address "12"] private boolean isAlphaNumber(String number) { // TODO: PhoneNumberUtils.isWellFormedSmsAddress() only check if the number is a valid // GSM SMS address. If the address contains a dialable char, it considers it a well // formed SMS addr. CDMA doesn't work that way and has a different parser for SMS // address (see CdmaSmsAddress.parse(String address)). We should definitely fix this!!! if (!PhoneNumberUtils.isWellFormedSmsAddress(number)) { // The example "T-Mobile" will exit here because there are no numbers. return true; // we're not an sms address, consider it an alpha number } if (MessageUtils.isAlias(number)) { return true; } number = PhoneNumberUtils.extractNetworkPortion(number); if (TextUtils.isEmpty(number)) { return true; // there are no digits whatsoever in the number } // At this point, anything like "Mobile1" or "Dogs77" will be stripped down to // "1" and "77". "#4#5#6#" remains as "#4#5#6#" at this point. return number.length() < 3; }
@Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) { AdapterView.AdapterContextMenuInfo menuInfo; try { menuInfo = (AdapterView.AdapterContextMenuInfo) menuInfoIn; } catch (ClassCastException e) { Log.e(TAG, "bad menuInfoIn", e); return; } Cursor cursor = (Cursor) mAdapter.getItem(menuInfo.position); String number = cursor.getString(NUMBER_COLUMN_INDEX); Uri numberUri = null; boolean isVoicemail = false; boolean isSipNumber = false; if (number.equals(CallerInfo.UNKNOWN_NUMBER)) { number = getString(R.string.unknown); } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) { number = getString(R.string.private_num); } else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) { number = getString(R.string.payphone); } else if (PhoneNumberUtils.extractNetworkPortion(number).equals(mVoiceMailNumber)) { number = getString(R.string.voicemail); numberUri = Uri.parse("voicemail:x"); isVoicemail = true; } else if (PhoneNumberUtils.isUriNumber(number)) { numberUri = Uri.fromParts("sip", number, null); isSipNumber = true; } else { numberUri = Uri.fromParts("tel", number, null); } ContactInfo info = mAdapter.getContactInfo(number); boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY); if (contactInfoPresent) { menu.setHeaderTitle(info.name); } else { menu.setHeaderTitle(number); } if (numberUri != null) { Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, numberUri); menu.add( 0, CONTEXT_MENU_CALL_CONTACT, 0, getResources().getString(R.string.recentCalls_callNumber, number)) .setIntent(intent); } if (contactInfoPresent) { Intent intent = new Intent( Intent.ACTION_VIEW, ContentUris.withAppendedId(Contacts.CONTENT_URI, info.personId)); StickyTabs.setTab(intent, getIntent()); menu.add(0, 0, 0, R.string.menu_viewContact).setIntent(intent); } if (numberUri != null && !isVoicemail && !isSipNumber) { menu.add(0, 0, 0, R.string.recentCalls_editNumberBeforeCall) .setIntent(new Intent(Intent.ACTION_DIAL, numberUri)); menu.add(0, 0, 0, R.string.menu_sendTextMessage) .setIntent(new Intent(Intent.ACTION_SENDTO, Uri.fromParts("sms", number, null))); } // "Add to contacts" item, if this entry isn't already associated with a contact if (!contactInfoPresent && numberUri != null && !isVoicemail && !isSipNumber) { // TODO: This item is currently disabled for SIP addresses, because // the Insert.PHONE extra only works correctly for PSTN numbers. // // To fix this for SIP addresses, we need to: // - define ContactsContract.Intents.Insert.SIP_ADDRESS, and use it here if // the current number is a SIP address // - update the contacts UI code to handle Insert.SIP_ADDRESS by // updating the SipAddress field // and then we can remove the "!isSipNumber" check above. Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); intent.setType(Contacts.CONTENT_ITEM_TYPE); intent.putExtra(Insert.PHONE, number); menu.add(0, 0, 0, R.string.recentCalls_addToContact).setIntent(intent); } menu.add(0, CONTEXT_MENU_ITEM_DELETE, 0, R.string.recentCalls_removeFromRecentList); }
public void bindView(Context context, View view, Cursor c) { final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag(); String number = c.getString(NUMBER_COLUMN_INDEX); String formattedNumber = null; String callerName = c.getString(CALLER_NAME_COLUMN_INDEX); int callerNumberType = c.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX); String callerNumberLabel = c.getString(CALLER_NUMBERLABEL_COLUMN_INDEX); // Store away the number so we can call it directly if you click on the call icon views.callView.setTag(number); // Lookup contacts with this number ContactInfo info = mContactInfo.get(number); if (info == null) { // Mark it as empty and queue up a request to find the name // The db request should happen on a non-UI thread info = ContactInfo.EMPTY; mContactInfo.put(number, info); enqueueRequest(number, c.getPosition(), callerName, callerNumberType, callerNumberLabel); } else if (info != ContactInfo.EMPTY) { // Has been queried // Check if any data is different from the data cached in the // calls db. If so, queue the request so that we can update // the calls db. if (!TextUtils.equals(info.name, callerName) || info.type != callerNumberType || !TextUtils.equals(info.label, callerNumberLabel)) { // Something is amiss, so sync up. enqueueRequest(number, c.getPosition(), callerName, callerNumberType, callerNumberLabel); } // Format and cache phone number for found contact if (info.formattedNumber == null) { info.formattedNumber = formatPhoneNumber(info.number); } formattedNumber = info.formattedNumber; } String name = info.name; int ntype = info.type; String label = info.label; // If there's no name cached in our hashmap, but there's one in the // calls db, use the one in the calls db. Otherwise the name in our // hashmap is more recent, so it has precedence. if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(callerName)) { name = callerName; ntype = callerNumberType; label = callerNumberLabel; // Format the cached call_log phone number formattedNumber = formatPhoneNumber(number); } // Set the text lines and call icon. // Assumes the call back feature is on most of the // time. For private and unknown numbers: hide it. views.callView.setVisibility(View.VISIBLE); if (!TextUtils.isEmpty(name)) { views.line1View.setText(name); views.labelView.setVisibility(View.VISIBLE); // "type" and "label" are currently unused for SIP addresses. CharSequence numberLabel = null; if (!PhoneNumberUtils.isUriNumber(number)) { numberLabel = Phone.getDisplayLabel(context, ntype, label, mLabelArray); } views.numberView.setVisibility(View.VISIBLE); views.numberView.setText(formattedNumber); if (!TextUtils.isEmpty(numberLabel)) { views.labelView.setText(numberLabel); views.labelView.setVisibility(View.VISIBLE); // Zero out the numberView's left margin (see below) ViewGroup.MarginLayoutParams numberLP = (ViewGroup.MarginLayoutParams) views.numberView.getLayoutParams(); numberLP.leftMargin = 0; views.numberView.setLayoutParams(numberLP); } else { // There's nothing to display in views.labelView, so hide it. // We can't set it to View.GONE, since it's the anchor for // numberView in the RelativeLayout, so make it INVISIBLE. // Also, we need to manually *subtract* some left margin from // numberView to compensate for the right margin built in to // labelView (otherwise the number will be indented by a very // slight amount). // TODO: a cleaner fix would be to contain both the label and // number inside a LinearLayout, and then set labelView *and* // its padding to GONE when there's no label to display. views.labelView.setText(null); views.labelView.setVisibility(View.INVISIBLE); ViewGroup.MarginLayoutParams labelLP = (ViewGroup.MarginLayoutParams) views.labelView.getLayoutParams(); ViewGroup.MarginLayoutParams numberLP = (ViewGroup.MarginLayoutParams) views.numberView.getLayoutParams(); // Equivalent to setting android:layout_marginLeft in XML numberLP.leftMargin = -labelLP.rightMargin; views.numberView.setLayoutParams(numberLP); } } else { if (number.equals(CallerInfo.UNKNOWN_NUMBER)) { number = getString(R.string.unknown); views.callView.setVisibility(View.INVISIBLE); } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) { number = getString(R.string.private_num); views.callView.setVisibility(View.INVISIBLE); } else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) { number = getString(R.string.payphone); } else if (PhoneNumberUtils.extractNetworkPortion(number).equals(mVoiceMailNumber)) { number = getString(R.string.voicemail); } else { // Just a raw number, and no cache, so format it nicely number = formatPhoneNumber(number); } views.line1View.setText(number); views.numberView.setVisibility(View.GONE); views.labelView.setVisibility(View.GONE); } long date = c.getLong(DATE_COLUMN_INDEX); // Set the date/time field by mixing relative and absolute times. int flags = DateUtils.FORMAT_ABBREV_RELATIVE; views.dateView.setText( DateUtils.getRelativeTimeSpanString( date, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, flags)); if (views.iconView != null) { int type = c.getInt(CALL_TYPE_COLUMN_INDEX); // Set the icon switch (type) { case Calls.INCOMING_TYPE: views.iconView.setImageDrawable(mDrawableIncoming); break; case Calls.OUTGOING_TYPE: views.iconView.setImageDrawable(mDrawableOutgoing); break; case Calls.MISSED_TYPE: views.iconView.setImageDrawable(mDrawableMissed); break; } } // Listen for the first draw if (mPreDrawListener == null) { mFirst = true; mPreDrawListener = this; view.getViewTreeObserver().addOnPreDrawListener(this); } }