I want to read Contacts via Contacts Picker like this:
Intent contactPickerIntent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
startActivityForResult(contact, CONTACT_PICK_CODE);
If I get the Result, the intent.getData() contains an uri to lookup the Contact, but I need the permission READ_CONTACTS to to read it.
I thought it may be possible to recieve a Contact without this permission, similar to the CALL permission: If I want to make a call directly, I need it, but without it I can send a number to the phone app, and the user must click on the call button. Is there a similar functionallity for READ_CONTACTS I'm not aware of?
You can retrieve Contact info without permissions and is something like you tell in the question.
In resume, you create an intent to pick a contact, this give you a URI (and temporally, also give you permissions to read it), then you use the URI to query to retrieve the data using Contact Provider API.
You can read more about it in Intents guide.
For example (from the guide):
static final int REQUEST_SELECT_PHONE_NUMBER = 1;
public void selectContact() {
// Start an activity for the user to pick a phone number from contacts
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(CommonDataKinds.Phone.CONTENT_TYPE);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, REQUEST_SELECT_PHONE_NUMBER);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_SELECT_PHONE_NUMBER && resultCode == RESULT_OK) {
// Get the URI and query the content provider for the phone number
Uri contactUri = data.getData();
String[] projection = new String[]{CommonDataKinds.Phone.NUMBER};
Cursor cursor = getContentResolver().query(contactUri, projection,
null, null, null);
// If the cursor returned is valid, get the phone number
if (cursor != null && cursor.moveToFirst()) {
int numberIndex = cursor.getColumnIndex(CommonDataKinds.Phone.NUMBER);
String number = cursor.getString(numberIndex);
// Do something with the phone number
...
}
}
}
I hope there isn't such a functionality.
The way you compare the "call without permissions" with the "read contacts without permissions" will be i think like the user has to enter the contactdata per hand.
The permissions have to be requested by app to protect the privacy of the user and helps to prevent data-collecting-apps.
If your app needs the contact list I think you can add the permission and the user will understand why you need it. If you don't need the contacts, you should not try the read the contacts.
An app always should only provide functionality that is really needed and nothing more.
If a desktop-programm will collect data of what you are doing on computer, what games you are playing, whit whom you are mailing, etc. you will call it a trojan.
So just read the contacts if its really needed and then the user will give you the permissions therefore.
The permission.system of android mostly make sense ;)
I have found, by experimentation, a few Contacts fields that can be queried successfully without READ_CONTACTS permission.
They are:
ContactsContract.Data.LOOKUP_KEY,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Data.DISPLAY_NAME_PRIMARY
However,
ContactsContract.CommonDataKinds.Phone.NUMBER
throws an IllegalArgumentException "invalid column: data1".
Similar exceptions were thrown by any of the other data columns that I tried.
I have tested this on many APIs, from 8 through 32. It fails in API 8, which requires READ_CONTACTS. It works in API 10 and up. I don't know about API 9.
In API 30+, any queries made must be announced in the AndroidManifest.xml <queries> elements.
This particular data makes sense in Android's strict security sense. This permits a client app to obtain a Uri by the user picking it from the Contacts app, together with an identifying name. The client app can later use this Uri to pull up the contact in the Contact app. No sensitive information is communicated -- the name is after all just a label that the user has applied to the contact.
I would love to know if and where this is documented.
Related
I received several crash report by my app with a java.lang.SecurityException. This occurs when the app try to obtain a persistent permission on a image URI user chosed from its images.
The method to choose images is through an intent:
public static Intent openGalleryToSelectImages(Activity a)
{
Intent intent = new Intent();
// Set action
if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
}
else {
intent.setAction(Intent.ACTION_GET_CONTENT);
}
// Set MIME type and allow multiple selection
intent.setType("image/*");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}
a.startActivityForResult(Intent.createChooser(intent,"Select Picture"), Communication.REQUEST_SELECT_IMAGES_FROM_GALLERY);
return intent;
}
Then I execute some things on the received array of URIs.
The crash occurs when, for each URI, I try to get persistent read permission:
activity.grantUriPermission(activity.getPackageName(), uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
but I cannot figure out what's wrong with that.
In addiction to this, the app works fine on several smartphones, so it seems bound to a specific vendor.
EDIT:
I cannot perform deep analysis on devices caused crash.
On my development device the URI for the images is something like:
content://com.android.providers.media.documents/document/image%3A796
You can't grant permissions to yourself.
You ask another app, such as Gallery, to give you an Uri to a resource and if that app is well written, it will grant your app read access to that Uri upon return.
Unfortunately some Gallery apps (including that of a prominent Android phone manufacturer) don't grant you anything.
This is how we end up having to request READ_EXTERNAL_STORAGE permission anyway. It also means that you need to request the runtime permission on Android 6+ when appropriate.
I ended up catching the SecurityException and requesting runtime permission at that moment - when I absolutely needed it.
Lesson learned the hard way: When you get back positive result in onRequestPermissionsResult, your activity is in stopped state. You can't save state and will lose any changes to member variables if you leave immediately (like by calling startActivity in the callback). Put what you need into member variables now but defer startActivity to onResume.
I use the Intent mechanism to have the user select an image via the standard way
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
ctx.startActivityForResult(intent, RequestCodes.SelectPhoto)
then I pass the Uri to another activity to maybe crop the photo. I need the Uri before to do some pre-checks.
On the Android emulators, the default providers such as Photos (apparently) give my whole app permission to open the Uri, not just the requesting activity. However, there is a "weird" provider in Asia, com.miui.gallery.provider.GalleryOpenProvider that doesn't -- an evil SecurityException happens in the cropper.
So I try to use ACTION_OPEN_DOCUMENT, which per the specs say that it will give my whole app permission until device reboot, but unfortunately that one doesn't support Google Photos in the cloud, in the emulator.
So I am looking for a way to determine if com.miui.gallery.provider.GalleryOpenProvider is going to be on the list for GET_CONTENT, and if so either prevent it, or otherwise fall back to using ACTION_OPEN_DOCUMENT. I'd like to avoid copying the stream before giving the Uri to the cropper, the crop activity treats it as readonly anyway.
This the full function to start the crop (kotlin). CropActivity is a modification of the old open-source Gallery app com.android.gallery3d.
private fun startCrop(ctx: Activity, uri: Uri) {
val intent = Intent(ctx, CropActivity::class.java)
intent.data = uri
val file = this.createImageFile(ctx, "photofinal")
if (file == null) {
this.showStorageUnavailable(ctx)
return
}
val outputUri = Uri.fromFile(file)
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri)
intent.putExtra(CropExtras.KEY_MIN_CROP_SIDE, Config.minimumImageDimension)
intent.putExtra(CropExtras.KEY_MOST_OBLONG_ASPECT, Config.maxPhotoAspectRatio)
intent.putExtra(CropExtras.KEY_EXIF_ORIENTATION, exifOrientation)
ctx.startActivityForResult(intent, RequestCodes.CropPhoto)
}
then I pass the Uri to another activity to maybe crop the photo
Pass that Uri in the "data" facet of the Intent, and add FLAG_GRANT_READ_URI_PERMISSION to transfer read access to the other component. See this sample app:
#Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
if (resultCode==Activity.RESULT_OK) {
getActivity()
.startService(new Intent(getActivity(), DurablizerService.class)
.setData(resultData.getData())
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION));
}
}
Here, I happen to be passing the Uri to a service, but the same principle holds for an activity.
See also this blog post for more about Uri access lifetimes.
Or, don't use separate activities, but do something else (e.g., multiple fragments).
On the Android emulators, the default providers such as Photos (apparently) give my whole app permission to open the Uri, not just the requesting activity.
That would occur if the Uri has a file scheme or is from an exported permission-less ContentProvider.
So I try to use ACTION_OPEN_DOCUMENT, which per the specs say that it will give my whole app permission until device reboot
It is subject to the same general rules as the Uri values you get from ACTION_GET_CONTENT.
So I am looking for a way to determine if com.miui.gallery.provider.GalleryOpenProvider is going to be on the list for GET_CONTENT
That's not strictly possible. Any app could return a Uri from that provider. In practice, that provider may only be used by its hosting app. If you found the package name for that provider's app, and you used queryIntentActivities() on PackageManager with your ACTION_GET_CONTENT Intent, you could determine if an activity from that app is in the list of ACTION_GET_CONTENT implementations.
However, if you use FLAG_GRANT_READ_URI_PERMISSION, as I note earlier, that should not be necessary.
if so either prevent it
Other than by rolling your own "chooser"-style UI, that's not strictly possible.
I am asking for images from a gallery app with:
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
I am reading the results like this:
ClipData clipData = data.getClipData();
Uri[] uris = new Uri[clipData.getItemCount()];
for (int i = 0; i < uris.length; i++) {
uris[i] = clipData.getItemAt(i).getUri();
}
I noticed that when I use Google Photos as the gallery, the results come back in a reverse order to that I have selected in Google Photos' ui.
Is that intentional, consistent or documented anywhere?
First, ACTION_PICK is the wrong Intent action. If you read the documentation for ACTION_PICK, that is for picking content from a specific collection; MIME type is not one of the documented input values. ACTION_GET_CONTENT is for picking content based on a MIME type.
Second, note that EXTRA_ALLOW_MULTIPLE is for use with ACTION_GET_CONTENT and ACTION_OPEN_DOCUMENT. Hence, few ACTION_PICK implementations will honor that extra.
Third, there is no requirement for the Uri values returned from an EXTRA_ALLOW_MULTIPLE request to be in any particular order. There are thousands of possible apps that will respond to your request, and they can send you the results in whatever order they wish. In particular, the documentation for EXTRA_ALLOW_MULTIPLE does not address order.
If order matters in your app, build your own UI to confirm the order. Offer a convenient one-click "reverse" option, in addition to perhaps drag-and-drop to allow for arbitrary changes. After all, the user may not realize, during the content selection, that order matters, and so even the order in which the user chose the content is not the user's actual desired order.
I am trying to access the Contact Data of an individual contact via intent. Recently, Google does not require the READ_CONTACTS permission when using an intent to read one contact. I am able to retrieve the name, phone number email and other data using this method, but retrieving the contact picture does not work. (If I add the READ_CONTACTS permission in the manifest it does work, but I don't want to have to add this permission as the app only tries to read contacts one at a time via an intent so the user is aware of it.) Here is the code below.
Starting Intent
Intent contactPickerIntent = new Intent(Intent.ACTION_PICK);
contactPickerIntent.setType(ContactsContract.Contacts.CONTENT_TYPE);
startActivityForResult(contactPickerIntent, CONTACT_PHOTO_STUDENT_RESULT);
Later in getting the result
if (requestCode == CONTACT_PHOTO_STUDENT_RESULT)
{ Uri contactUri = data.getData();
ContentResolver cr = getContentResolver();
InputStream is = ContactsContract.Contacts.openContactPhotoInputStream(cr, contactUri);
Then I use the inputstream to save as a jpg file
}
I want is to hide all contacts without phone numbers in phonebook from my application..
Just like phonebook do, When you go to phonebook -> settings there is a checkbox which states that "Contacts with phone number only" I want to implement this feature in my app
I need a method (code) to navigate users to
phonebook -> settings (activity) (System app)
from my application activity.
or worse case hide all contacts without phone number through database. So that phonebook can filter out.
Currently i found
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intent, 123);
Above code opens phone book but i want to open phone book -> settings page.
In sum i want to make phonebook contents "contacts with phone numbers" from my application
I need a method (code) to navigate users to phonebook -> settings (activity) (System app)
There are hundreds, perhaps thousands, of Android phones. None will necessarily be the same with respect to their "phonebook" app. None of those "phonebook" apps necessarily have the feature you seek -- some may, some many not. And, most likely, none have a documented and supported Intent structure for getting to a screen within the app to control the setting that they may or may not have.
I want is to hide all contacts without phone numbers in phonebook from my application
Then you will need to not use the "phonebook" app, but instead display contacts yourself, via the READ_CONTACTS permission and the ContactsContract ContentProvider.
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intent, PICK_CONTACT);
Well, I have sucessfully developed a Contacts application for Android as my major project. I believe this is quite simple. Here is code how I did it.
Cursor c = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
null,
ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1",
null,
ContactsContract.Contacts.DISPLAY_NAME+" COLLATE LOCALIZED ASC");
mAdapter = new MyAdapter(this,
R.layout.single_cell,
c,
new String[]{ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME},
new int[]{R.id.disp_name},
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
listview.setAdapter(mAdapter);
And, in MyAdapter, I have extended SimpleCursorAdapter and over rided bindView() to take advantage of efficiency of SimpleCursorAdapter. However, you need a permission to read contacts. In your android-manifest file. Please mention,
<uses-permission android:name="android.permission.READ_CONTACTS"/>
Hope, it helps.