0

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?

Schorschii
  • 11
  • 3

0 Answers0