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;
 }
Пример #2
0
 /**
  * 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));
   }
 }
Пример #4
0
  /**
   * 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;
  }