/** Method to add listeners to a currently running query */
  public void addQueryListener(int token, OnQueryCompleteListener listener, Object cookie) {

    if (DBG)
      log("adding listener to query: " + mHandler.mQueryUri + " handler: " + mHandler.toString());

    // create cookieWrapper, add query request to end of queue.
    CookieWrapper cw = new CookieWrapper();
    cw.listener = listener;
    cw.cookie = cookie;
    cw.event = EVENT_ADD_LISTENER;

    mHandler.startQuery(token, cw, null, null, null, null, null);
  }
  /** Factory method to start query with a number */
  public static CallerInfoAsyncQuery startQuery(
      int token, Context context, String number, OnQueryCompleteListener listener, Object cookie) {
    // contruct the URI object and start Query.
    Uri contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));

    CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
    c.allocate(context, contactRef);

    if (DBG) log("starting query for number: " + number + " handler: " + c.toString());

    // create cookieWrapper, start query
    CookieWrapper cw = new CookieWrapper();
    cw.listener = listener;
    cw.cookie = cookie;
    cw.number = number;

    // check to see if these are recognized numbers, and use shortcuts if we can.
    if (PhoneNumberUtils.isEmergencyNumber(number)) {
      cw.event = EVENT_EMERGENCY_NUMBER;
    } else if (PhoneNumberUtils.isVoiceMailNumber(number)) {
      cw.event = EVENT_VOICEMAIL_NUMBER;
    } else {
      cw.event = EVENT_NEW_QUERY;
    }

    c.mHandler.startQuery(token, cw, contactRef, null, null, null, null);

    return c;
  }
  /** Factory method to start query with a Uri query spec */
  public static CallerInfoAsyncQuery startQuery(
      int token, Context context, Uri contactRef, OnQueryCompleteListener listener, Object cookie) {

    CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
    c.allocate(context, contactRef);

    if (DBG) log("starting query for URI: " + contactRef + " handler: " + c.toString());

    // create cookieWrapper, start query
    CookieWrapper cw = new CookieWrapper();
    cw.listener = listener;
    cw.cookie = cookie;
    cw.event = EVENT_NEW_QUERY;

    c.mHandler.startQuery(token, cw, contactRef, null, null, null, null);

    return c;
  }
    /**
     * Overrides onQueryComplete from AsyncQueryHandler.
     *
     * <p>This method takes into account the state of this class; we construct the CallerInfo object
     * only once for each set of listeners. When the query thread has done its work and calls this
     * method, we inform the remaining listeners in the queue, until we're out of listeners. Once we
     * get the message indicating that we should expect no new listeners for this CallerInfo object,
     * we release the AsyncCursorInfo back into the pool.
     */
    @Override
    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
      if (DBG) log("query complete for token: " + token);

      // get the cookie and notify the listener.
      CookieWrapper cw = (CookieWrapper) cookie;
      if (cw == null) {
        // Normally, this should never be the case for calls originating
        // from within this code.
        // However, if there is any code that calls this method, we should
        // check the parameters to make sure they're viable.
        if (DBG) log("Cookie is null, ignoring onQueryComplete() request.");
        return;
      }

      if (cw.event == EVENT_END_OF_QUEUE) {
        release();
        return;
      }

      // check the token and if needed, create the callerinfo object.
      if (mCallerInfo == null) {
        if ((mQueryContext == null) || (mQueryUri == null)) {
          throw new QueryPoolException(
              "Bad context or query uri, or CallerInfoAsyncQuery already released.");
        }

        // adjust the callerInfo data as needed, and only if it was set from the
        // initial query request.
        // Change the callerInfo number ONLY if it is an emergency number or the
        // voicemail number, and adjust other data (including photoResource)
        // accordingly.
        if (cw.event == EVENT_EMERGENCY_NUMBER) {
          // Note we're setting the phone number here (refer to javadoc
          // comments at the top of CallerInfo class).
          mCallerInfo = new CallerInfo().markAsEmergency(mQueryContext);
        } else if (cw.event == EVENT_VOICEMAIL_NUMBER) {
          mCallerInfo = new CallerInfo().markAsVoiceMail();
        } else {
          mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor);
          // Use the number entered by the user for display.
          if (!TextUtils.isEmpty(cw.number)) {
            mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number);
          }
        }

        if (DBG) log("constructing CallerInfo object for token: " + token);

        // notify that we can clean up the queue after this.
        CookieWrapper endMarker = new CookieWrapper();
        endMarker.event = EVENT_END_OF_QUEUE;
        startQuery(token, endMarker, null, null, null, null, null);
      }

      // notify the listener that the query is complete.
      if (cw.listener != null) {
        if (DBG)
          log(
              "notifying listener: "
                  + cw.listener.getClass().toString()
                  + " for token: "
                  + token
                  + mCallerInfo);
        cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
      }
    }