private boolean isSIPCall(String number, Intent intent) { boolean sipCall = false; String scheme = ""; if (intent.getData() != null) { scheme = intent.getData().getScheme(); if ((scheme != null) && ("sip".equals(scheme) || PhoneNumberUtils.isUriNumber(number))) { sipCall = true; } } Log.d(TAG, "isSIPCall : " + sipCall); return sipCall; }
/** * Performs another lookup if previous lookup fails and it's a SIP call and the peer's username is * all numeric. Look up the username as it could be a PSTN number in the contact database. * * @param context the query context * @param number the original phone number, could be a SIP URI * @param previousResult the result of previous lookup * @return previousResult if it's not the case */ static CallerInfo doSecondaryLookupIfNecessary( Context context, String number, CallerInfo previousResult) { if (!previousResult.contactExists && PhoneNumberUtils.isUriNumber(number)) { String username = PhoneNumberUtils.getUsernameFromUriNumber(number); if (PhoneNumberUtils.isGlobalPhoneNumber(username)) { previousResult = getCallerInfo( context, Uri.withAppendedPath( PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(username))); } } return previousResult; }
public void onClick(View view) { String number = (String) view.getTag(); if (!TextUtils.isEmpty(number)) { // Here, "number" can either be a PSTN phone number or a // SIP address. So turn it into either a tel: URI or a // sip: URI, as appropriate. Uri callUri; if (PhoneNumberUtils.isUriNumber(number)) { callUri = Uri.fromParts("sip", number, null); } else { callUri = Uri.fromParts("tel", number, null); } StickyTabs.saveTab(RecentCallsListActivity.this, getIntent()); startActivity(new Intent(Intent.ACTION_CALL_PRIVILEGED, callUri)); } }
/** * Retrieve the phone number from the caller info or the connection. * * <p>For incoming call the number is in the Connection object. For outgoing call we use the * CallerInfo phoneNumber field if present. All the processing should have been done already (CDMA * vs GSM numbers). * * <p>If CallerInfo is missing the phone number, get it from the connection. Apply the Call Name * Presentation (CNAP) transform in the connection on the number. * * @param conn The phone connection. * @param callerInfo The CallerInfo. Maybe null. * @return the phone number. */ private String getLogNumber(Connection conn, CallerInfo callerInfo) { String number = null; if (conn.isIncoming()) { number = conn.getAddress(); } else { // For emergency and voicemail calls, // CallerInfo.phoneNumber does *not* contain a valid phone // number. Instead it contains an I18N'd string such as // "Emergency Number" or "Voice Mail" so we get the number // from the connection. if (null == callerInfo || TextUtils.isEmpty(callerInfo.phoneNumber) || callerInfo.isEmergencyNumber() || callerInfo.isVoiceMailNumber()) { if (conn.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { // In cdma getAddress() is not always equals to getOrigDialString(). number = conn.getOrigDialString(); } else { number = conn.getAddress(); } } else { number = callerInfo.phoneNumber; } } if (null == number) { return null; } else { int presentation = conn.getNumberPresentation(); // Do final CNAP modifications. String newNumber = PhoneUtils.modifyForSpecialCnapCases(mApplication, callerInfo, number, presentation); if (!PhoneNumberUtils.isUriNumber(number)) { number = PhoneNumberUtils.stripSeparators(number); } if (VDBG) log("getLogNumber: " + number); return number; } }
/** * Format the given phone number using {@link PhoneNumberUtils#formatNumber(android.text.Editable, * int)}. This helper method uses {@link #sEditable} and {@link #sFormattingType} to prevent * allocations between multiple calls. * * <p>Because of the shared {@link #sEditable} builder, <b>this method is not thread safe</b>, and * should only be called from the GUI thread. * * <p>If the given String object is null or empty, return an empty String. */ private String formatPhoneNumber(String number) { if (TextUtils.isEmpty(number)) { return ""; } // If "number" is really a SIP address, don't try to do any formatting at all. if (PhoneNumberUtils.isUriNumber(number)) { return number; } // Cache formatting type if not already present if (sFormattingType == FORMATTING_TYPE_INVALID) { sFormattingType = PhoneNumberUtils.getFormatTypeForLocale(Locale.getDefault()); } sEditable.clear(); sEditable.append(number); PhoneNumberUtils.formatNumber(sEditable, sFormattingType); return sEditable.toString(); }
private void callEntry(int position) { if (position < 0) { // In touch mode you may often not have something selected, so // just call the first entry to make sure that [send] [send] calls the // most recent entry. position = 0; } final Cursor cursor = (Cursor) mAdapter.getItem(position); if (cursor != null) { String number = cursor.getString(NUMBER_COLUMN_INDEX); if (TextUtils.isEmpty(number) || number.equals(CallerInfo.UNKNOWN_NUMBER) || number.equals(CallerInfo.PRIVATE_NUMBER) || number.equals(CallerInfo.PAYPHONE_NUMBER)) { // This number can't be called, do nothing return; } Intent intent; // If "number" is really a SIP address, construct a sip: URI. if (PhoneNumberUtils.isUriNumber(number)) { intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, Uri.fromParts("sip", number, null)); } else { // We're calling a regular PSTN phone number. // Construct a tel: URI, but do some other possible cleanup first. int callType = cursor.getInt(CALL_TYPE_COLUMN_INDEX); if (!number.startsWith("+") && (callType == Calls.INCOMING_TYPE || callType == Calls.MISSED_TYPE)) { // If the caller-id matches a contact with a better qualified number, use it number = getBetterNumberFromContacts(number); } intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, Uri.fromParts("tel", number, null)); } StickyTabs.saveTab(this, getIntent()); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); startActivity(intent); } }
private void processIntent(Intent intent) { String action = intent.getAction(); intent.putExtra(SUBSCRIPTION_KEY, mSubscription); Log.d(TAG, "outGoingcallBroadCaster action is" + action); String number = PhoneNumberUtils.getNumberFromIntent(intent, this); Log.d(TAG, " number from Intent : " + number); // Check the number, don't convert for sip uri // TODO put uriNumber under PhoneNumberUtils if (number != null) { if (!PhoneNumberUtils.isUriNumber(number)) { number = PhoneNumberUtils.convertKeypadLettersToDigits(number); number = PhoneNumberUtils.stripSeparators(number); } } // If true, this flag will indicate that the current call is a special kind // of call (most likely an emergency number) that 3rd parties aren't allowed // to intercept or affect in any way. (In that case, we start the call // immediately rather than going through the NEW_OUTGOING_CALL sequence.) boolean callNow; if (getClass().getName().equals(intent.getComponent().getClassName())) { // If we were launched directly from the OutgoingCallBroadcaster, // not one of its more privileged aliases, then make sure that // only the non-privileged actions are allowed. if (!Intent.ACTION_CALL.equals(intent.getAction())) { Log.w(TAG, "Attempt to deliver non-CALL action; forcing to CALL"); intent.setAction(Intent.ACTION_CALL); } } // Check whether or not this is an emergency number, in order to // enforce the restriction that only the CALL_PRIVILEGED and // CALL_EMERGENCY intents are allowed to make emergency calls. // // (Note that the ACTION_CALL check below depends on the result of // isPotentialLocalEmergencyNumber() rather than just plain // isLocalEmergencyNumber(), to be 100% certain that we *don't* // allow 3rd party apps to make emergency calls by passing in an // "invalid" number like "9111234" that isn't technically an // emergency number but might still result in an emergency call // with some networks.) final boolean isExactEmergencyNumber = (number != null) && PhoneNumberUtils.isLocalEmergencyNumber(number, this); final boolean isPotentialEmergencyNumber = (number != null) && PhoneNumberUtils.isPotentialLocalEmergencyNumber(number, this); if (VDBG) { Log.v(TAG, "- Checking restrictions for number '" + number + "':"); Log.v(TAG, " isExactEmergencyNumber = " + isExactEmergencyNumber); Log.v(TAG, " isPotentialEmergencyNumber = " + isPotentialEmergencyNumber); } /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */ // TODO: This code is redundant with some code in InCallScreen: refactor. if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) { // We're handling a CALL_PRIVILEGED intent, so we know this request came // from a trusted source (like the built-in dialer.) So even a number // that's *potentially* an emergency number can safely be promoted to // CALL_EMERGENCY (since we *should* allow you to dial "91112345" from // the dialer if you really want to.) action = isPotentialEmergencyNumber ? Intent.ACTION_CALL_EMERGENCY : Intent.ACTION_CALL; if (DBG) Log.v(TAG, "- updating action from CALL_PRIVILEGED to " + action); intent.setAction(action); } if (Intent.ACTION_CALL.equals(action)) { if (isPotentialEmergencyNumber) { Log.w( TAG, "Cannot call potential emergency number '" + number + "' with CALL Intent " + intent + "."); Log.i(TAG, "Launching default dialer instead..."); Intent invokeFrameworkDialer = new Intent(); // TwelveKeyDialer is in a tab so we really want // DialtactsActivity. Build the intent 'manually' to // use the java resolver to find the dialer class (as // opposed to a Context which look up known android // packages only) invokeFrameworkDialer.setClassName( "com.android.contacts", "com.android.contacts.DialtactsActivity"); invokeFrameworkDialer.setAction(Intent.ACTION_DIAL); invokeFrameworkDialer.setData(intent.getData()); invokeFrameworkDialer.putExtra(SUBSCRIPTION_KEY, mSubscription); if (DBG) Log.v(TAG, "onCreate(): calling startActivity for Dialer: " + invokeFrameworkDialer); startActivity(invokeFrameworkDialer); finish(); return; } intent.putExtra(SUBSCRIPTION_KEY, mSubscription); Log.d(TAG, "for non emergency call,sub is :" + mSubscription); callNow = false; } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) { // ACTION_CALL_EMERGENCY case: this is either a CALL_PRIVILEGED // intent that we just turned into a CALL_EMERGENCY intent (see // above), or else it really is an CALL_EMERGENCY intent that // came directly from some other app (e.g. the EmergencyDialer // activity built in to the Phone app.) // Make sure it's at least *possible* that this is really an // emergency number. if (!isPotentialEmergencyNumber) { Log.w( TAG, "Cannot call non-potential-emergency number " + number + " with EMERGENCY_CALL Intent " + intent + "."); finish(); return; } int sub = PhoneApp.getInstance().getVoiceSubscriptionInService(); intent.putExtra(SUBSCRIPTION_KEY, sub); Log.d(TAG, "Attempting emergency call on sub :" + sub); callNow = true; } else { Log.e(TAG, "Unhandled Intent " + intent + "."); finish(); return; } // Make sure the screen is turned on. This is probably the right // thing to do, and more importantly it works around an issue in the // activity manager where we will not launch activities consistently // when the screen is off (since it is trying to keep them paused // and has... issues). // // Also, this ensures the device stays awake while doing the following // broadcast; technically we should be holding a wake lock here // as well. PhoneApp.getInstance().wakeUpScreen(); /* If number is null, we're probably trying to call a non-existent voicemail number, * send an empty flash or something else is fishy. Whatever the problem, there's no * number, so there's no point in allowing apps to modify the number. */ if (number == null || TextUtils.isEmpty(number)) { if (intent.getBooleanExtra(EXTRA_SEND_EMPTY_FLASH, false)) { Log.i(TAG, "onCreate: SEND_EMPTY_FLASH..."); PhoneUtils.sendEmptyFlash(PhoneApp.getInstance().getPhone()); finish(); return; } else { Log.i(TAG, "onCreate: null or empty number, setting callNow=true..."); callNow = true; intent.putExtra(SUBSCRIPTION_KEY, mSubscription); } } if (callNow) { // This is a special kind of call (most likely an emergency number) // that 3rd parties aren't allowed to intercept or affect in any way. // So initiate the outgoing call immediately. if (DBG) Log.v(TAG, "onCreate(): callNow case! Calling placeCall(): " + intent); // Initiate the outgoing call, and simultaneously launch the // InCallScreen to display the in-call UI: PhoneApp.getInstance().callController.placeCall(intent); // Note we do *not* "return" here, but instead continue and // send the ACTION_NEW_OUTGOING_CALL broadcast like for any // other outgoing call. (But when the broadcast finally // reaches the OutgoingCallReceiver, we'll know not to // initiate the call again because of the presence of the // EXTRA_ALREADY_CALLED extra.) } // For now, SIP calls will be processed directly without a // NEW_OUTGOING_CALL broadcast. // // TODO: In the future, though, 3rd party apps *should* be allowed to // intercept outgoing calls to SIP addresses as well. To do this, we should // (1) update the NEW_OUTGOING_CALL intent documentation to explain this // case, and (2) pass the outgoing SIP address by *not* overloading the // EXTRA_PHONE_NUMBER extra, but instead using a new separate extra to hold // the outgoing SIP address. (Be sure to document whether it's a URI or just // a plain address, whether it could be a tel: URI, etc.) Uri uri = intent.getData(); String scheme = uri.getScheme(); if (Constants.SCHEME_SIP.equals(scheme) || PhoneNumberUtils.isUriNumber(number)) { startSipCallOptionHandler(this, intent, uri, number); finish(); return; // TODO: if there's ever a way for SIP calls to trigger a // "callNow=true" case (see above), we'll need to handle that // case here too (most likely by just doing nothing at all.) } final String callOrigin = intent.getStringExtra(PhoneApp.EXTRA_CALL_ORIGIN); if (callOrigin != null) { if (DBG) Log.v(TAG, "Call origin is passed (" + callOrigin + ")"); PhoneApp.getInstance().setLatestActiveCallOrigin(callOrigin); } else { if (DBG) Log.v(TAG, "Call origin is not passed. Reset current one."); PhoneApp.getInstance().setLatestActiveCallOrigin(null); } Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL); if (number != null) { broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number); } PhoneUtils.checkAndCopyPhoneProviderExtras(intent, broadcastIntent); broadcastIntent.putExtra(EXTRA_ALREADY_CALLED, callNow); broadcastIntent.putExtra(EXTRA_ORIGINAL_URI, uri.toString()); broadcastIntent.putExtra(SUBSCRIPTION_KEY, mSubscription); if (DBG) Log.v(TAG, "Broadcasting intent: " + broadcastIntent + "."); sendOrderedBroadcast( broadcastIntent, PERMISSION, new OutgoingCallReceiver(), null, // scheduler Activity.RESULT_OK, // initialCode number, // initialData: initial value for the result data null); // initialExtras }
@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); } }
private boolean queryContactInfo(CallerInfoQuery ciq) { // First check if there was a prior request for the same number // that was already satisfied ContactInfo info = mContactInfo.get(ciq.number); boolean needNotify = false; if (info != null && info != ContactInfo.EMPTY) { return true; } else { // Ok, do a fresh Contacts lookup for ciq.number. boolean infoUpdated = false; if (PhoneNumberUtils.isUriNumber(ciq.number)) { // This "number" is really a SIP address. // TODO: This code is duplicated from the // CallerInfoAsyncQuery class. To avoid that, could the // code here just use CallerInfoAsyncQuery, rather than // manually running ContentResolver.query() itself? // We look up SIP addresses directly in the Data table: Uri contactRef = Data.CONTENT_URI; // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent. // // Also note we use "upper(data1)" in the WHERE clause, and // uppercase the incoming SIP address, in order to do a // case-insensitive match. // // TODO: May also need to normalize by adding "sip:" as a // prefix, if we start storing SIP addresses that way in the // database. String selection = "upper(" + Data.DATA1 + ")=?" + " AND " + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'"; String[] selectionArgs = new String[] {ciq.number.toUpperCase()}; Cursor dataTableCursor = RecentCallsListActivity.this .getContentResolver() .query( contactRef, null, // projection selection, // selection selectionArgs, // selectionArgs null); // sortOrder if (dataTableCursor != null) { if (dataTableCursor.moveToFirst()) { info = new ContactInfo(); // TODO: we could slightly speed this up using an // explicit projection (and thus not have to do // those getColumnIndex() calls) but the benefit is // very minimal. // Note the Data.CONTACT_ID column here is // equivalent to the PERSON_ID_COLUMN_INDEX column // we use with "phonesCursor" below. info.personId = dataTableCursor.getLong(dataTableCursor.getColumnIndex(Data.CONTACT_ID)); info.name = dataTableCursor.getString(dataTableCursor.getColumnIndex(Data.DISPLAY_NAME)); // "type" and "label" are currently unused for SIP addresses info.type = SipAddress.TYPE_OTHER; info.label = null; // And "number" is the SIP address. // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent. info.number = dataTableCursor.getString(dataTableCursor.getColumnIndex(Data.DATA1)); infoUpdated = true; } dataTableCursor.close(); } } else { // "number" is a regular phone number, so use the // PhoneLookup table: Cursor phonesCursor = RecentCallsListActivity.this .getContentResolver() .query( Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(ciq.number)), PHONES_PROJECTION, null, null, null); if (phonesCursor != null) { if (phonesCursor.moveToFirst()) { info = new ContactInfo(); info.personId = phonesCursor.getLong(PERSON_ID_COLUMN_INDEX); info.name = phonesCursor.getString(NAME_COLUMN_INDEX); info.type = phonesCursor.getInt(PHONE_TYPE_COLUMN_INDEX); info.label = phonesCursor.getString(LABEL_COLUMN_INDEX); info.number = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX); infoUpdated = true; } phonesCursor.close(); } } if (infoUpdated) { // New incoming phone number invalidates our formatted // cache. Any cache fills happen only on the GUI thread. info.formattedNumber = null; mContactInfo.put(ciq.number, info); // Inform list to update this item, if in view needNotify = true; } } if (info != null) { updateCallLog(ciq, info); } return needNotify; }
/** * @return true if the "Respond via SMS" feature should be enabled for the specified incoming * call. * <p>The general rule is that we *do* allow "Respond via SMS" except for the few (relatively * rare) cases where we know for sure it won't work, namely: - a bogus or blank incoming * number - a call from a SIP address - a "call presentation" that doesn't allow the number to * be revealed * <p>In all other cases, we allow the user to respond via SMS. * <p>Note that this behavior isn't perfect; for example we have no way to detect whether the * incoming call is from a landline (with most networks at least), so we still enable this * feature even though SMSes to that number will silently fail. */ public static boolean allowRespondViaSmsForCall(Context context, Call ringingCall) { if (DBG) log("allowRespondViaSmsForCall(" + ringingCall + ")..."); // First some basic sanity checks: if (ringingCall == null) { Log.w(TAG, "allowRespondViaSmsForCall: null ringingCall!"); return false; } if (!ringingCall.isRinging()) { // The call is in some state other than INCOMING or WAITING! // (This should almost never happen, but it *could* // conceivably happen if the ringing call got disconnected by // the network just *after* we got it from the CallManager.) Log.w( TAG, "allowRespondViaSmsForCall: ringingCall not ringing! state = " + ringingCall.getState()); return false; } Connection conn = ringingCall.getLatestConnection(); if (conn == null) { // The call doesn't have any connections! (Again, this can // happen if the ringing call disconnects at the exact right // moment, but should almost never happen in practice.) Log.w(TAG, "allowRespondViaSmsForCall: null Connection!"); return false; } // Check the incoming number: final String number = conn.getAddress(); if (DBG) log("- number: '" + number + "'"); if (TextUtils.isEmpty(number)) { Log.w(TAG, "allowRespondViaSmsForCall: no incoming number!"); return false; } if (PhoneNumberUtils.isUriNumber(number)) { // The incoming number is actually a URI (i.e. a SIP address), // not a regular PSTN phone number, and we can't send SMSes to // SIP addresses. // (TODO: That might still be possible eventually, though. Is // there some SIP-specific equivalent to sending a text message?) Log.i(TAG, "allowRespondViaSmsForCall: incoming 'number' is a SIP address."); return false; } // Finally, check the "call presentation": int presentation = conn.getNumberPresentation(); if (DBG) log("- presentation: " + presentation); if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) { // PRESENTATION_RESTRICTED means "caller-id blocked". // The user isn't allowed to see the number in the first // place, so obviously we can't let you send an SMS to it. Log.i(TAG, "allowRespondViaSmsForCall: PRESENTATION_RESTRICTED."); return false; } // Allow the feature only when there's a destination for it. if (context.getPackageManager().resolveService(getInstantTextIntent(number, null), 0) == null) { return false; } // TODO: with some carriers (in certain countries) you *can* actually // tell whether a given number is a mobile phone or not. So in that // case we could potentially return false here if the incoming call is // from a land line. // If none of the above special cases apply, it's OK to enable the // "Respond via SMS" feature. return true; }