Challenge: calls the named suspect


#1

I have tried to implement this challenge. I think I came a long way but it is not perfect yet.

I have added a new button to the fragment_crime.xml layout file:

<Button android:id="@+id/crime_callSuspectButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:text="@string/crime_call_suspect_text" />

I have implemented a listener for this button:

[code]mCallSuspectButton = (Button)v.findViewById(R.id.crime_callSuspectButton);
mCallSuspectButton.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
			
	if (mCrime.getSuspect() != null){
				
		// Create implicit intent with 'action' = ACTION_PICK and 'uri' = 'contacts' to pick a phone numbers (a contact can have multiple)//
		Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
		// Explicitly set the 'type' to 'phone numbers': the contacts-app will display all and only phone numbers of contacts //
	       intent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);	
		startActivityForResult(intent, REQUEST_PHONENR);
	}
			
}

});[/code]

Next I have extended the onActivityResult(…) method:

[code] // Results from Contacts-app //
else if (requestCode == REQUEST_PHONENR){
// The data of the Intent is the URI that points to a single record in the PHONE-DB-TABLE //
Uri phoneUri = data.getData();

		// Specify which fields you want the query to return //
		String[] queryFields = new String[]{Phone.NUMBER};
		
		// Perform the query - the phoneUri acts like a 'where'-clause //
		Cursor c = getActivity().getContentResolver().query( phoneUri, 		// the uri for the content to retrieve; here a specific record from the phone-db-table
											      	queryFields, 	// which columns to return
											      	null, 			// 'where'-clause
											      	null, 			// ..?
											      	null);			// 'order by'-clause
		
		// Check that we actually have results //
		if (c.getCount() == 0){
			c.close();
			return;
		}
		
		// Extract the first column of the first row, this is the contact's phone-number //
		c.moveToFirst();
		String number = c.getString(0);
		Log.i(TAG, "Selected number: " + number);
		c.close();
		
		// Try to dial the number //
		Uri numberUri = Uri.parse("tel:" + number);
		Intent intent = new Intent(Intent.ACTION_DIAL, numberUri);
		startActivity(intent);
	}[/code]

The above works. As far as I can see now there is just one ‘but’:
When trying to pick the phone number, the contacts app shows all available contacts. I do not know how to tell the contacts app to only display phone numbers of a specific contact.

*** EDIT ***
I have tried to change the code so it would only be possible to pick phone numbers from a specific contact (the suspect).

First I have changed the code where we store the suspects display_name. Here I not only fetch the display_name but also the contact’s ‘lookup_key’ and ‘id’.

[code]…
// Specify which fields you want the query to return //
String[] queryFields = new String[]{ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts._ID};

// Perform the query - the contactUri acts like a ‘where’-clause //
Cursor c = getActivity().getContentResolver().query(contactUri, // the uri for the content to retrieve; here a specific contact-record from the contacts-db
queryFields, // which columns to return
null, // ‘where’-clause
null, // …?
null); // ‘order by’-clause

// Check that we actually have results //
if (c.getCount() == 0){
c.close();
return;
}

// Extract the first column of the first row, this is the contact’s name //
c.moveToFirst();
String suspect = c.getString(0);
mCrime.setSuspect(suspect);

String suspectId = c.getString(1) + “/” + c.getString(2);
mCrime.setSuspectId(suspectId);
…[/code]

Second I have changed the code where I create the implicit intent to pick a phone number:

... // Create implicit intent with 'action' = ACTION_PICK and 'data' = 'uri to contacts' // Intent intent = new Intent(Intent.ACTION_PICK); //, ContactsContract.Contacts.CONTENT_URI Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, mCrime.getSuspectId()); intent.setData(uri); ...

In the above code I create an uri which should point to a specific contact… but its not working!

here is a link to the help page:
http://developer.android.com/reference/android/provider/ContactsContract.Contacts.html#CONTENT_LOOKUP_URI


#2

I implemented the ‘call suspect’ challenge a little differently but it may help:

  1. Created Suspect interface with id, name, phone properties; its implemented by SuspectBean class.
  2. Changed ‘Crime’ object Suspect property to be a ‘Suspect’ object (rather than String)
  3. Update JSONSerializer to serialize the Crime’s new Suspect property
  4. Queried to get Suspect’s phone number when they are initially selected.

The code below is from CrimeFragment.onActivityResult(). First it gets the Contact selected by the User; then queries for the Contact’s phone number and then finally it create’s a new instance of Suspect and assigns it to the Crime:

}else if (requestCode == REQUEST_CONTACT){

//query for Contact

Uri contactUri = data.getData();
Cursor c = getActivity().getContentResolver().query(contactUri, null, null, null, null);
if(c.getCount() == 0){
     c.close();
     return;
}
c.moveToFirst();

//get the Contact's Id & name

String suspectId = c.getString(c.getColumnIndex(ContactsContract.Contacts._ID));
String suspectName = c.getString(c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
String telephone = null;

//query for the phone number...if they have one
boolean hasPhone = Integer.parseInt(c.getString(c.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0;
if(hasPhone){
     Cursor pCur = getActivity().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, 
               null,
               ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
               new String[]{suspectId}, null);
     if(pCur.getCount() == 0){
          pCur.close();
     }
     pCur.moveToFirst();
     telephone = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
     pCur.close();
}

//assign values to new instance of Suspect
Suspect aSuspect = new SuspectBean();
aSuspect.setId(suspectId);
aSuspect.setName(suspectName);
aSuspect.setPhoneNumber(telephone);

mCrime.setSuspect(aSuspect);

//update suspect button text and ensure call button is enabled
mSuspectButton.setText("Prime Suspect: " + suspectName);
mCallButton.setEnabled(true);
c.close();

This makes onClick() method in the ‘Call’ button’s listener very straightforward, since all of the searching is done…

@Override 
public void onClick(View v){
     if(mCrime.getSuspect() != null && mCrime.getSuspect().getPhoneNumber() != null){
          Intent i = new Intent(Intent.ACTION_DIAL);
          i.setData(Uri.parse("tel:" + mCrime.getSuspect().getPhoneNumber()));
          startActivity(i);
     }
}

Hope it helps…
Devon


#3

Definitely an elegant solution - adding the suspect object is nice since the suspect info persists and you don’t have to look it up again from Contacts each time the activity fires!


#4

Devon’s approach works well but does seem to require read contacts permission (android.permission.READ_CONTACTS). Is there a way to get the phone number on the initial query without this additional permission?

PS. I’m curious because I feel Android users will eventually start paying attention to the seemingly unrelated long list of permissions required by many apps.

Stan


#5

Devon, could you share your other code of this challenge. Because I am really couldn’t understand how you manage with this task.


#6

My solution is similar to Devon’s and requires the contacts permission.

[code] @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
return;
}
[…]
else if (requestCode == REQUEST_CONTACT) {
Uri contactUri = data.getData();

		// Specify which fields you want your query to return values for.
		String[] queryFields = new String[] {
			ContactsContract.Contacts.DISPLAY_NAME,
			ContactsContract.Contacts._ID
		};
		
		// Perform your query - the contactUri is like a "where" clause here.
		Cursor c = getActivity().getContentResolver().query(contactUri, queryFields, null, null, null);
		
		// Double check that you actually got results.
		if (c.getCount() == 0) {
			c.close();
			return;
		}
		
		// Pull out the first column of the first row of data. That is the suspect's name.
		c.moveToFirst();
		String suspect = c.getString(0);
		mCrime.setSuspect(suspect);
		mSuspectButton.setText(suspect);
		
		// Second column will be the contact ID
		String id = c.getString(1);
		
		// Use the ID to get the phone number.
		c = getActivity().getContentResolver().query(
				ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
				ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = "+ id, null, null);
        
		// Bail if no results.
		if (c.getCount() == 0) {
			mCrime.setSuspectNum(null);
			mDialButton.setEnabled(false);
			c.close();
			return;
		}
		
		// Store the digits and enable the dial button.
		c.moveToFirst();
        mCrime.setSuspectNum(c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
        mDialButton.setEnabled(true);
		c.close();
	}
}

[/code]


#7

Ok, I got it, without using any permission.
So, we must use another URI set in the intent we use in onClick method.

@Override public void onClick(View v) { Intent i = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI); if(checkIntentSafe(i)) startActivityForResult(i, REQUEST_CONTACT); }

after I check what could we get from the ContactsContract.CommonDataKinds.Phone, there is same display name like the ContactsContract.Contact one. So I set the query fields like this.

[code]String[] queryFields = new String[]{
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER,
};
//Execute query - contactUri is like ‘where’ clause
Cursor c = getActivity().getContentResolver().query(contactUri, queryFields, null, null, null);
//Double check and closing cursor if null
if (c.getCount() == 0){
c.close();
return;
}

		//Pulling out the first column of first row - the suspect name
		c.moveToFirst();
		String suspect = c.getString(0);
		String number = c.getString(1);
		mCrime.setSuspect(suspect);
		mSuspectButton.setText(suspect);
		mCrime.setSuspectNumber(number);
		
		c.close();[/code]