Inspired by this answer, I'm using the following code to provide caller ID for incoming calls. It works great in Emulator on Android 7.0 and newer. But not with Android 6 or lower.
AndroidManifest.xml:
<provider
android:name=".tools.CallerIdProvider"
android:authorities="@string/callerid_authority"
android:readPermission="android.permission.READ_CONTACTS"
android:enabled="true"
android:exported="true">
<meta-data
android:name="android.content.ContactDirectory"
android:value="true"/>
</provider>
ContentProvider Class:
public class CallerIdProvider extends ContentProvider {
private final static int DIRECTORIES = 1;
private final static int PHONE_LOOKUP = 2;
private final static int PRIMARY_PHOTO = 3;
private final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private Uri authorityUri;
private CustomerDatabase mDb;
@Override
public boolean onCreate() {
String authority = getContext().getString(R.string.callerid_authority);
authorityUri = Uri.parse("content://"+authority);
uriMatcher.addURI(authority, "directories", DIRECTORIES);
uriMatcher.addURI(authority, "phone_lookup/*", PHONE_LOOKUP);
uriMatcher.addURI(authority, "photo/primary_photo/*", PRIMARY_PHOTO);
mDb = new CustomerDatabase(getContext());
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
Log.e("callerid", uri.toString());
try {
MatrixCursor cursor = new MatrixCursor(strings);
ArrayList<Object> values = new ArrayList<>();
switch (uriMatcher.match(uri)) {
case (DIRECTORIES):
for (String c : strings) {
switch (c) {
case (ContactsContract.Directory.PHOTO_SUPPORT):
values.add(ContactsContract.Directory.PHOTO_SUPPORT_FULL);
break;
case (ContactsContract.Directory.ACCOUNT_NAME):
case (ContactsContract.Directory.ACCOUNT_TYPE):
case (ContactsContract.Directory.DISPLAY_NAME):
values.add(getContext().getString(R.string.app_name));
break;
case (ContactsContract.Directory.TYPE_RESOURCE_ID):
values.add(R.string.app_name);
break;
case (ContactsContract.Directory.EXPORT_SUPPORT):
values.add(ContactsContract.Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY);
break;
case (ContactsContract.Directory.SHORTCUT_SUPPORT):
values.add(ContactsContract.Directory.SHORTCUT_SUPPORT_NONE);
break;
default:
values.add(null);
}
}
cursor.addRow(values.toArray());
return cursor;
case (PHONE_LOOKUP):
String incomingNumber = uri.getPathSegments().get(1);
Customer customer = mDb.getCustomerByNumber(incomingNumber);
if (customer != null) {
for (String c : strings) {
switch (c) {
case (ContactsContract.PhoneLookup._ID):
values.add(customer.mId);
break;
case (ContactsContract.PhoneLookup.DISPLAY_NAME):
values.add(customer.getFullName(false));
break;
case (ContactsContract.PhoneLookup.LABEL):
values.add(customer.mCustomerGroup);
break;
case (ContactsContract.PhoneLookup.PHOTO_THUMBNAIL_URI):
case (ContactsContract.PhoneLookup.PHOTO_URI):
values.add(
Uri.withAppendedPath(
Uri.withAppendedPath(
Uri.withAppendedPath(
authorityUri,
"photo"
),
"primary_photo"
),
incomingNumber
)
);
break;
default:
values.add(null);
}
}
}
cursor.addRow(values.toArray());
return cursor;
default:
Log.e("callerid", "not handled:" + uri.toString());
}
} catch(Exception e) {
Log.e("callerid", "crash:" + e.toString());
}
Log.e("callerid", "cursor return null");
return null;
}
@Override
public AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode) {
if(uriMatcher.match(uri) == PRIMARY_PHOTO) {
String incomingNumber = uri.getPathSegments().get(2);
Customer customer = mDb.getCustomerByNumber(incomingNumber);
if(customer != null && customer.getImage().length > 0) {
return bytesToAssetFileDescriptor(customer.getImage());
} else {
return getContext().getResources().openRawResourceFd(R.drawable.logo_customerdb_raw);
}
}
Log.e("callerid", "asset return null");
return null;
}
private AssetFileDescriptor bytesToAssetFileDescriptor(byte[] data) {
try {
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
InputStream inputStream = new ByteArrayInputStream(data);
ParcelFileDescriptor.AutoCloseOutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]);
int len;
while((len = inputStream.read()) >= 0) {
outputStream.write(len);
}
inputStream.close();
outputStream.flush();
outputStream.close();
return new AssetFileDescriptor(pipe[0], 0, AssetFileDescriptor.UNKNOWN_LENGTH);
} catch(IOException ignored) {
return null;
}
}
...
}
The error log shows, that only content://<appid>/directories is called on Android 6, but not content://<appip>/phone_lookup/<number>. Does anybody know what's wrong with my code?