blob: 4a93c618aafd1d4e3f23e89ff45b15733669d17f [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.appsearch.contactsindexer;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.Data;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.server.appsearch.contactsindexer.appsearchtypes.Person;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Helper Class to handle data for different MIME types from CP2, and build {@link Person} from
* them.
*
* @hide
*/
public final class ContactDataHandler {
private final Map<String, DataHandler> mHandlers;
private final Set<String> mNeededColumns;
/** Constructor. */
public ContactDataHandler(Resources resources) {
// Create handlers for different MIME types
mHandlers = new ArrayMap<>();
mHandlers.put(Email.CONTENT_ITEM_TYPE, new EmailDataHandler(resources));
mHandlers.put(Nickname.CONTENT_ITEM_TYPE, new NicknameDataHandler());
mHandlers.put(Phone.CONTENT_ITEM_TYPE, new PhoneHandler(resources));
mHandlers.put(StructuredPostal.CONTENT_ITEM_TYPE, new StructuredPostalHandler(resources));
mHandlers.put(StructuredName.CONTENT_ITEM_TYPE, new StructuredNameHandler());
// Retrieve all the needed columns from different data handlers.
Set<String> neededColumns = new ArraySet<>();
neededColumns.add(ContactsContract.Data.MIMETYPE);
for (DataHandler handler : mHandlers.values()) {
handler.addNeededColumns(neededColumns);
}
// We need to make sure this is unmodifiable since the reference is returned in
// getNeededColumns().
mNeededColumns = Collections.unmodifiableSet(neededColumns);
}
/**
* Adds the information of the current row from {@link ContactsContract.Data} table
* into the {@link PersonBuilderHelper}.
*
* <p>By reading each row in the table, we will get the detailed information about a
* Person(contact).
*
* @param builderHelper a helper to build the {@link Person}.
*/
public void convertCursorToPerson(@NonNull Cursor cursor,
@NonNull PersonBuilderHelper builderHelper) {
Objects.requireNonNull(cursor);
Objects.requireNonNull(builderHelper);
int mimetypeIndex = cursor.getColumnIndex(Data.MIMETYPE);
String mimeType = cursor.getString(mimetypeIndex);
DataHandler handler = mHandlers.get(mimeType);
if (handler != null) {
handler.addData(builderHelper, cursor);
}
}
abstract static class DataHandler {
/** Gets the column as a string. */
@Nullable
protected final String getColumnString(@NonNull Cursor cursor, @NonNull String column) {
Objects.requireNonNull(cursor);
Objects.requireNonNull(column);
int columnIndex = cursor.getColumnIndex(column);
if (columnIndex == -1) {
return null;
}
return cursor.getString(columnIndex);
}
/** Gets the column as an int. */
protected final int getColumnInt(@NonNull Cursor cursor, @NonNull String column) {
Objects.requireNonNull(cursor);
Objects.requireNonNull(column);
int columnIndex = cursor.getColumnIndex(column);
if (columnIndex == -1) {
return 0;
}
return cursor.getInt(columnIndex);
}
/** Adds the columns needed for the {@code DataHandler}. */
public abstract void addNeededColumns(Collection<String> columns);
/** Adds the data into {@link PersonBuilderHelper}. */
public abstract void addData(@NonNull PersonBuilderHelper builderHelper, Cursor cursor);
}
private abstract static class SingleColumnDataHandler extends DataHandler {
private final String mColumn;
protected SingleColumnDataHandler(@NonNull String column) {
Objects.requireNonNull(column);
mColumn = column;
}
/** Adds the columns needed for the {@code DataHandler}. */
@Override
public final void addNeededColumns(@NonNull Collection<String> columns) {
Objects.requireNonNull(columns);
columns.add(mColumn);
}
/** Adds the data into {@link PersonBuilderHelper}. */
@Override
public final void addData(@NonNull PersonBuilderHelper builderHelper,
@NonNull Cursor cursor) {
Objects.requireNonNull(builderHelper);
Objects.requireNonNull(cursor);
String data = getColumnString(cursor, mColumn);
if (!TextUtils.isEmpty(data)) {
addSingleColumnStringData(builderHelper, data);
}
}
protected abstract void addSingleColumnStringData(PersonBuilderHelper builderHelper,
String data);
}
private abstract static class ContactPointDataHandler extends DataHandler {
private final Resources mResources;
private final String mDataColumn;
private final String mTypeColumn;
private final String mLabelColumn;
public ContactPointDataHandler(
@NonNull Resources resources, @NonNull String dataColumn,
@NonNull String typeColumn, @NonNull String labelColumn) {
mResources = Objects.requireNonNull(resources);
mDataColumn = Objects.requireNonNull(dataColumn);
mTypeColumn = Objects.requireNonNull(typeColumn);
mLabelColumn = Objects.requireNonNull(labelColumn);
}
/** Adds the columns needed for the {@code DataHandler}. */
@Override
public final void addNeededColumns(@NonNull Collection<String> columns) {
Objects.requireNonNull(columns);
columns.add(Data._ID);
columns.add(Data.IS_PRIMARY);
columns.add(Data.IS_SUPER_PRIMARY);
columns.add(mDataColumn);
columns.add(mTypeColumn);
columns.add(mLabelColumn);
}
/**
* Adds the data for ContactsPoint(email, telephone, postal addresses) into
* {@link Person.Builder}.
*/
@Override
public final void addData(@NonNull PersonBuilderHelper builderHelper,
@NonNull Cursor cursor) {
Objects.requireNonNull(builderHelper);
Objects.requireNonNull(cursor);
String data = getColumnString(cursor, mDataColumn);
if (!TextUtils.isEmpty(mDataColumn)) {
// get the corresponding label to the type.
int type = getColumnInt(cursor, mTypeColumn);
String label = getTypeLabel(mResources, type,
getColumnString(cursor, mLabelColumn));
addContactPointData(builderHelper, label, data);
}
}
@NonNull
protected abstract String getTypeLabel(Resources resources, int type, String label);
/**
* Adds the information in the {@link Person.Builder}.
*
* @param builderHelper a helper to build the {@link Person}.
* @param label the corresponding label to the {@code type} for the data.
* @param data data read from the designed column in the row.
*/
protected abstract void addContactPointData(
PersonBuilderHelper builderHelper, String label, String data);
}
private static final class EmailDataHandler extends ContactPointDataHandler {
public EmailDataHandler(@NonNull Resources resources) {
super(resources, Email.ADDRESS, Email.TYPE, Email.LABEL);
}
/**
* Adds the Email information in the {@link Person.Builder}.
*
* @param builderHelper a builder to build the {@link Person}.
* @param label The corresponding label to the {@code type}. E.g. {@link
* com.android.internal.R.string#emailTypeHome} to {@link
* Email#TYPE_HOME} or custom label for the data if {@code type} is
* {@link
* Email#TYPE_CUSTOM}.
* @param data data read from the designed column {@code Email.ADDRESS} in the row.
*/
@Override
protected void addContactPointData(
@NonNull PersonBuilderHelper builderHelper, @NonNull String label,
@NonNull String data) {
Objects.requireNonNull(builderHelper);
Objects.requireNonNull(data);
Objects.requireNonNull(label);
builderHelper.addEmailToPerson(label, data);
}
@NonNull
@Override
protected String getTypeLabel(@NonNull Resources resources, int type,
@NonNull String label) {
Objects.requireNonNull(resources);
Objects.requireNonNull(label);
return Email.getTypeLabel(resources, type, label).toString();
}
}
private static final class PhoneHandler extends ContactPointDataHandler {
public PhoneHandler(@NonNull Resources resources) {
super(resources, Phone.NUMBER, Phone.TYPE, Phone.LABEL);
}
/**
* Adds the phone number information in the {@link Person.Builder}.
*
* @param builderHelper helper to build the {@link Person}.
* @param label corresponding label to {@code type}. E.g. {@link
* com.android.internal.R.string#phoneTypeHome} to {@link
* Phone#TYPE_HOME}, or custom label for the data if {@code type} is
* {@link Phone#TYPE_CUSTOM}.
* @param data data read from the designed column {@link Phone#NUMBER} in the row.
*/
@Override
protected void addContactPointData(
@NonNull PersonBuilderHelper builderHelper, @NonNull String label,
@NonNull String data) {
Objects.requireNonNull(builderHelper);
Objects.requireNonNull(data);
Objects.requireNonNull(label);
builderHelper.addPhoneToPerson(label, data);
}
@NonNull
@Override
protected String getTypeLabel(@NonNull Resources resources, int type,
@NonNull String label) {
Objects.requireNonNull(resources);
Objects.requireNonNull(label);
return Phone.getTypeLabel(resources, type, label).toString();
}
}
private static final class StructuredPostalHandler extends ContactPointDataHandler {
public StructuredPostalHandler(@NonNull Resources resources) {
super(
resources,
StructuredPostal.FORMATTED_ADDRESS,
StructuredPostal.TYPE,
StructuredPostal.LABEL);
}
/**
* Adds the postal address information in the {@link Person.Builder}.
*
* @param builderHelper helper to build the {@link Person}.
* @param label corresponding label to {@code type}. E.g. {@link
* com.android.internal.R.string#postalTypeHome} to {@link
* StructuredPostal#TYPE_HOME}, or custom label for the data if {@code
* type} is {@link StructuredPostal#TYPE_CUSTOM}.
* @param data data read from the designed column
* {@link StructuredPostal#FORMATTED_ADDRESS} in the row.
*/
@Override
protected void addContactPointData(
@NonNull PersonBuilderHelper builderHelper, @NonNull String label,
@NonNull String data) {
Objects.requireNonNull(builderHelper);
Objects.requireNonNull(data);
Objects.requireNonNull(label);
builderHelper.addAddressToPerson(label, data);
}
@NonNull
@Override
protected String getTypeLabel(@NonNull Resources resources, int type,
@NonNull String label) {
Objects.requireNonNull(resources);
Objects.requireNonNull(label);
return StructuredPostal.getTypeLabel(resources, type, label).toString();
}
}
private static final class NicknameDataHandler extends SingleColumnDataHandler {
protected NicknameDataHandler() {
super(Nickname.NAME);
}
@Override
protected void addSingleColumnStringData(@NonNull PersonBuilderHelper builder,
@NonNull String data) {
Objects.requireNonNull(builder);
Objects.requireNonNull(data);
builder.getPersonBuilder().addAdditionalName(data);
}
}
private static final class StructuredNameHandler extends DataHandler {
private static final String[] COLUMNS = {
Data.RAW_CONTACT_ID,
Data.NAME_RAW_CONTACT_ID,
// Only those three fields we need to set in the builder.
StructuredName.GIVEN_NAME,
StructuredName.MIDDLE_NAME,
StructuredName.FAMILY_NAME,
};
/** Adds the columns needed for the {@code DataHandler}. */
@Override
public final void addNeededColumns(Collection<String> columns) {
Collections.addAll(columns, COLUMNS);
}
/** Adds the data into {@link Person.Builder}. */
@Override
public final void addData(@NonNull PersonBuilderHelper builderHelper, Cursor cursor) {
Objects.requireNonNull(builderHelper);
String rawContactId = getColumnString(cursor, Data.RAW_CONTACT_ID);
String nameRawContactId = getColumnString(cursor, Data.NAME_RAW_CONTACT_ID);
String givenName = getColumnString(cursor, StructuredName.GIVEN_NAME);
String familyName = getColumnString(cursor, StructuredName.FAMILY_NAME);
String middleName = getColumnString(cursor, StructuredName.MIDDLE_NAME);
Person.Builder builder = builderHelper.getPersonBuilder();
// only set given, middle and family name iff rawContactId is same as
// nameRawContactId. In this case those three match the value for displayName in CP2.
if (!TextUtils.isEmpty(rawContactId)
&& !TextUtils.isEmpty(nameRawContactId)
&& rawContactId.equals(nameRawContactId)) {
if (givenName != null) {
builder.setGivenName(givenName);
}
if (familyName != null) {
builder.setFamilyName(familyName);
}
if (middleName != null) {
builder.setMiddleName(middleName);
}
}
}
}
}