Merge "DO NOT MERGE: Check permission before accessing contacts provider" into rvc-qpr-dev
diff --git a/car-telephony-common/src/com/android/car/telephony/common/AsyncQueryLiveData.java b/car-telephony-common/src/com/android/car/telephony/common/AsyncQueryLiveData.java
index ecacb51..c4f6170 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/AsyncQueryLiveData.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/AsyncQueryLiveData.java
@@ -51,8 +51,7 @@
public AsyncQueryLiveData(Context context, QueryParam.Provider provider,
ExecutorService executorService) {
- mObservableAsyncQuery = new ObservableAsyncQuery(provider, context.getContentResolver(),
- this::onCursorLoaded);
+ mObservableAsyncQuery = new ObservableAsyncQuery(context, provider, this::onCursorLoaded);
mExecutorService = executorService;
}
diff --git a/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java b/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
index 8ed7e73..3bdf52a 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
@@ -16,6 +16,7 @@
package com.android.car.telephony.common;
+import android.Manifest;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract;
@@ -119,7 +120,8 @@
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE,
ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE},
- ContactsContract.Contacts.DISPLAY_NAME + " ASC ");
+ ContactsContract.Contacts.DISPLAY_NAME + " ASC ",
+ Manifest.permission.READ_CONTACTS);
mContactListAsyncQueryLiveData = new AsyncQueryLiveData<List<Contact>>(mContext,
QueryParam.of(contactListQueryParam), Executors.newSingleThreadExecutor()) {
@Override
diff --git a/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java b/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java
index 394b6d4..9f2f0cb 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java
@@ -18,12 +18,15 @@
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.database.Cursor;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
import com.android.car.apps.common.log.L;
@@ -48,6 +51,7 @@
void onQueryFinished(@Nullable Cursor cursor);
}
+ private Context mContext;
private AsyncQueryHandler mAsyncQueryHandler;
private QueryParam.Provider mQueryParamProvider;
private OnQueryFinishedListener mOnQueryFinishedListener;
@@ -61,11 +65,12 @@
* @param listener Listener which will be called when data is available.
*/
public ObservableAsyncQuery(
+ @NonNull Context context,
@NonNull QueryParam.Provider queryParamProvider,
- @NonNull ContentResolver cr,
@NonNull OnQueryFinishedListener listener) {
- mAsyncQueryHandler = new AsyncQueryHandlerImpl(this, cr);
- mContentResolver = cr;
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mAsyncQueryHandler = new AsyncQueryHandlerImpl(this, mContentResolver);
mContentObserver = new ContentObserver(mAsyncQueryHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -88,7 +93,8 @@
mToken++;
QueryParam queryParam = mQueryParamProvider.getQueryParam();
- if (queryParam != null) {
+ if (queryParam != null && ContextCompat.checkSelfPermission(mContext,
+ queryParam.mPermission) == PackageManager.PERMISSION_GRANTED) {
mAsyncQueryHandler.startQuery(
mToken,
null,
diff --git a/car-telephony-common/src/com/android/car/telephony/common/QueryParam.java b/car-telephony-common/src/com/android/car/telephony/common/QueryParam.java
index 9628124..6ceb7c5 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/QueryParam.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/QueryParam.java
@@ -53,17 +53,21 @@
final String[] mSelectionArgs;
/** Used by {@link ObservableAsyncQuery#startQuery()} as query param. */
final String mOrderBy;
+ /** Used by {@link ObservableAsyncQuery#startQuery()} to check query permission. */
+ final String mPermission;
public QueryParam(
@NonNull Uri uri,
@Nullable String[] projection,
@Nullable String selection,
@Nullable String[] selectionArgs,
- @Nullable String orderBy) {
+ @Nullable String orderBy,
+ @NonNull String permission) {
mUri = uri;
mProjection = projection;
mSelection = selection;
mSelectionArgs = selectionArgs;
mOrderBy = orderBy;
+ mPermission = permission;
}
}
diff --git a/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java b/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java
index 40d93a7..a80a854 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java
@@ -42,6 +42,7 @@
import android.widget.ImageView;
import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
@@ -187,17 +188,21 @@
public static final class PhoneNumberInfo {
private final String mPhoneNumber;
private final String mDisplayName;
+ private final String mDisplayNameAlt;
private final String mInitials;
private final Uri mAvatarUri;
private final String mTypeLabel;
+ private final String mLookupKey;
- public PhoneNumberInfo(String phoneNumber, String displayName,
- String initials, Uri avatarUri, String typeLabel) {
+ public PhoneNumberInfo(String phoneNumber, String displayName, String displayNameAlt,
+ String initials, Uri avatarUri, String typeLabel, String lookupKey) {
mPhoneNumber = phoneNumber;
mDisplayName = displayName;
+ mDisplayNameAlt = displayNameAlt;
mInitials = initials;
mAvatarUri = avatarUri;
mTypeLabel = typeLabel;
+ mLookupKey = lookupKey;
}
public String getPhoneNumber() {
@@ -208,6 +213,10 @@
return mDisplayName;
}
+ public String getDisplayNameAlt() {
+ return mDisplayNameAlt;
+ }
+
/**
* Returns the initials of the contact related to the phone number. Returns null if there is
* no related contact.
@@ -226,6 +235,12 @@
return mTypeLabel;
}
+ /** Returns the lookup key of the contact if any is found. */
+ @Nullable
+ public String getLookupKey() {
+ return mLookupKey;
+ }
+
}
/**
@@ -240,97 +255,127 @@
return CompletableFuture.completedFuture(new PhoneNumberInfo(
number,
context.getString(R.string.unknown),
+ context.getString(R.string.unknown),
null,
null,
- ""));
+ "",
+ null));
}
if (isVoicemailNumber(context, number)) {
return CompletableFuture.completedFuture(new PhoneNumberInfo(
number,
context.getString(R.string.voicemail),
+ context.getString(R.string.voicemail),
null,
makeResourceUri(context, R.drawable.ic_voicemail),
- ""));
+ "",
+ null));
}
- if (InMemoryPhoneBook.isInitialized()) {
- Contact contact = InMemoryPhoneBook.get().lookupContactEntry(number);
- if (contact != null) {
- String name = contact.getDisplayName();
- if (name == null) {
- name = getFormattedNumber(context, number);
- }
+ return CompletableFuture.supplyAsync(() -> lookupNumberInBackground(context, number));
+ }
- if (name == null) {
- name = context.getString(R.string.unknown);
- }
+ /** Lookup phone number info in background. */
+ @WorkerThread
+ public static PhoneNumberInfo lookupNumberInBackground(Context context, String number) {
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ String readableNumber = getReadableNumber(context, number);
+ return new PhoneNumberInfo(number, readableNumber, readableNumber, null, null, null,
+ null);
+ }
- PhoneNumber phoneNumber = contact.getPhoneNumber(context, number);
- CharSequence typeLabel = "";
- if (phoneNumber != null) {
- typeLabel = Phone.getTypeLabel(context.getResources(),
- phoneNumber.getType(),
- phoneNumber.getLabel());
- }
+ Contact contact = InMemoryPhoneBook.get().lookupContactEntry(number);
+ if (contact != null) {
+ String name = contact.getDisplayName();
+ String nameAlt = contact.getDisplayNameAlt();
+ if (TextUtils.isEmpty(name)) {
+ name = getReadableNumber(context, number);
+ }
+ if (TextUtils.isEmpty(nameAlt)) {
+ nameAlt = name;
+ }
- return CompletableFuture.completedFuture(new PhoneNumberInfo(
- number,
- name,
- contact.getInitials(),
- contact.getAvatarUri(),
- typeLabel.toString()));
+ PhoneNumber phoneNumber = contact.getPhoneNumber(context, number);
+ CharSequence typeLabel = phoneNumber == null ? "" : phoneNumber.getReadableLabel(
+ context.getResources());
+
+ return new PhoneNumberInfo(
+ number,
+ name,
+ nameAlt,
+ contact.getInitials(),
+ contact.getAvatarUri(),
+ typeLabel.toString(),
+ contact.getLookupKey());
+ }
+
+ String name = null;
+ String nameAlt = null;
+ String initials = null;
+ String photoUriString = null;
+ CharSequence typeLabel = "";
+ String lookupKey = null;
+
+ ContentResolver cr = context.getContentResolver();
+ try (Cursor cursor = cr.query(
+ Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
+ new String[]{
+ PhoneLookup.DISPLAY_NAME,
+ PhoneLookup.DISPLAY_NAME_ALTERNATIVE,
+ PhoneLookup.PHOTO_URI,
+ PhoneLookup.TYPE,
+ PhoneLookup.LABEL,
+ PhoneLookup.LOOKUP_KEY,
+ },
+ null, null, null)) {
+
+ if (cursor != null && cursor.moveToFirst()) {
+ int nameColumn = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
+ int altNameColumn = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME_ALTERNATIVE);
+ int photoUriColumn = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
+ int typeColumn = cursor.getColumnIndex(PhoneLookup.TYPE);
+ int labelColumn = cursor.getColumnIndex(PhoneLookup.LABEL);
+ int lookupKeyColumn = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
+
+ name = cursor.getString(nameColumn);
+ nameAlt = cursor.getString(altNameColumn);
+ photoUriString = cursor.getString(photoUriColumn);
+ initials = getInitials(name, nameAlt);
+
+ int type = cursor.getInt(typeColumn);
+ String label = cursor.getString(labelColumn);
+ typeLabel = Phone.getTypeLabel(context.getResources(), type, label);
+
+ lookupKey = cursor.getString(lookupKeyColumn);
}
}
- return CompletableFuture.supplyAsync(() -> {
- String name = null;
- String nameAlt = null;
- String photoUriString = null;
- CharSequence typeLabel = "";
- ContentResolver cr = context.getContentResolver();
- String initials;
- try (Cursor cursor = cr.query(
- Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
- new String[]{
- PhoneLookup.DISPLAY_NAME,
- PhoneLookup.DISPLAY_NAME_ALTERNATIVE,
- PhoneLookup.PHOTO_URI,
- PhoneLookup.TYPE,
- PhoneLookup.LABEL,
- },
- null, null, null)) {
+ if (TextUtils.isEmpty(name)) {
+ name = getReadableNumber(context, number);
+ }
+ if (TextUtils.isEmpty(nameAlt)) {
+ nameAlt = name;
+ }
- if (cursor != null && cursor.moveToFirst()) {
- int nameColumn = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
- int altNameColumn = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME_ALTERNATIVE);
- int photoUriColumn = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
- int typeColumn = cursor.getColumnIndex(PhoneLookup.TYPE);
- int labelColumn = cursor.getColumnIndex(PhoneLookup.LABEL);
+ return new PhoneNumberInfo(
+ number,
+ name,
+ nameAlt,
+ initials,
+ TextUtils.isEmpty(photoUriString) ? null : Uri.parse(photoUriString),
+ typeLabel.toString(),
+ lookupKey);
+ }
- name = cursor.getString(nameColumn);
- nameAlt = cursor.getString(altNameColumn);
- photoUriString = cursor.getString(photoUriColumn);
- int type = cursor.getInt(typeColumn);
- String label = cursor.getString(labelColumn);
- typeLabel = Phone.getTypeLabel(context.getResources(), type, label);
- }
- }
+ private static String getReadableNumber(Context context, String number) {
+ String readableNumber = getFormattedNumber(context, number);
- initials = getInitials(name, nameAlt);
-
- if (name == null) {
- name = getFormattedNumber(context, number);
- }
-
- if (name == null) {
- name = context.getString(R.string.unknown);
- }
-
- return new PhoneNumberInfo(number, name, initials,
- TextUtils.isEmpty(photoUriString) ? null : Uri.parse(photoUriString),
- typeLabel.toString());
- });
+ if (readableNumber == null) {
+ readableNumber = context.getString(R.string.unknown);
+ }
+ return readableNumber;
}
/**
@@ -476,6 +521,11 @@
* Set the given phone number as the primary phone number for its associated contact.
*/
public static void setAsPrimaryPhoneNumber(Context context, PhoneNumber phoneNumber) {
+ if (context.checkSelfPermission(Manifest.permission.WRITE_CONTACTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ L.w(TAG, "Missing WRITE_CONTACTS permission, not setting primary number.");
+ return;
+ }
// Update the primary values in the data record.
ContentValues values = new ContentValues(1);
values.put(ContactsContract.Data.IS_SUPER_PRIMARY, 1);
@@ -487,23 +537,6 @@
}
/**
- * Add a contact to favorite or remove it from favorite.
- */
- public static int setAsFavoriteContact(Context context, Contact contact, boolean isFavorite) {
- if (contact.isStarred() == isFavorite) {
- return 0;
- }
-
- ContentValues values = new ContentValues(1);
- values.put(ContactsContract.Contacts.STARRED, isFavorite ? 1 : 0);
-
- String where = ContactsContract.Contacts._ID + " = ?";
- String[] selectionArgs = new String[]{Long.toString(contact.getId())};
- return context.getContentResolver().update(ContactsContract.Contacts.CONTENT_URI, values,
- where, selectionArgs);
- }
-
- /**
* Mark missed call log matching given phone number as read. If phone number string is not
* valid, it will mark all new missed call log as read.
*/