Merge "Fix snippetizing cursor"
This commit is contained in:
committed by
Android (Google) Code Review
commit
5e8752595f
@@ -34,6 +34,7 @@ import android.database.Cursor;
|
|||||||
import android.database.DatabaseUtils;
|
import android.database.DatabaseUtils;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
@@ -44,6 +45,9 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -166,6 +170,22 @@ public final class ContactsContract {
|
|||||||
*/
|
*/
|
||||||
public static final String STREQUENT_PHONE_ONLY = "strequent_phone_only";
|
public static final String STREQUENT_PHONE_ONLY = "strequent_phone_only";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A key to a boolean in the "extras" bundle of the cursor.
|
||||||
|
* The boolean indicates that the provider did not create a snippet and that the client asking
|
||||||
|
* for the snippet should do it (true means the snippeting was deferred to the client).
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final String DEFERRED_SNIPPETING = "deferred_snippeting";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key to retrieve the original query on the client side.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final String DEFERRED_SNIPPETING_QUERY = "deferred_snippeting_query";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
@@ -4857,6 +4877,19 @@ public final class ContactsContract {
|
|||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public static final String SNIPPET_ARGS_PARAM_KEY = "snippet_args";
|
public static final String SNIPPET_ARGS_PARAM_KEY = "snippet_args";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A key to ask the provider to defer the snippeting to the client if possible.
|
||||||
|
* Value of 1 implies true, 0 implies false when 0 is the default.
|
||||||
|
* When a cursor is returned to the client, it should check for an extra with the name
|
||||||
|
* {@link ContactsContract#DEFERRED_SNIPPETING} in the cursor. If it exists, the client
|
||||||
|
* should do its own snippeting using {@link ContactsContract#snippetize}. If
|
||||||
|
* it doesn't exist, the snippet column in the cursor should already contain a snippetized
|
||||||
|
* string.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final String DEFERRED_SNIPPETING_KEY = "deferred_snippeting";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -8054,4 +8087,138 @@ public final class ContactsContract {
|
|||||||
public static final String DATA_SET = "com.android.contacts.extra.DATA_SET";
|
public static final String DATA_SET = "com.android.contacts.extra.DATA_SET";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a snippet out of the given content that matches the given query.
|
||||||
|
* @param content - The content to use to compute the snippet.
|
||||||
|
* @param displayName - Display name for the contact - if this already contains the search
|
||||||
|
* content, no snippet should be shown.
|
||||||
|
* @param query - String to search for in the content.
|
||||||
|
* @param snippetStartMatch - Marks the start of the matching string in the snippet.
|
||||||
|
* @param snippetEndMatch - Marks the end of the matching string in the snippet.
|
||||||
|
* @param snippetEllipsis - Ellipsis string appended to the end of the snippet (if too long).
|
||||||
|
* @param snippetMaxTokens - Maximum number of words from the snippet that will be displayed.
|
||||||
|
* @return The computed snippet, or null if the snippet could not be computed or should not be
|
||||||
|
* shown.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static String snippetize(String content, String displayName, String query,
|
||||||
|
char snippetStartMatch, char snippetEndMatch, String snippetEllipsis,
|
||||||
|
int snippetMaxTokens) {
|
||||||
|
|
||||||
|
String lowerQuery = query != null ? query.toLowerCase() : null;
|
||||||
|
if (TextUtils.isEmpty(content) || TextUtils.isEmpty(query) ||
|
||||||
|
TextUtils.isEmpty(displayName) || !content.toLowerCase().contains(lowerQuery)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the display name already contains the query term, return empty - snippets should
|
||||||
|
// not be needed in that case.
|
||||||
|
String lowerDisplayName = displayName != null ? displayName.toLowerCase() : "";
|
||||||
|
List<String> nameTokens = new ArrayList<String>();
|
||||||
|
List<Integer> nameTokenOffsets = new ArrayList<Integer>();
|
||||||
|
split(lowerDisplayName.trim(), nameTokens, nameTokenOffsets);
|
||||||
|
for (String nameToken : nameTokens) {
|
||||||
|
if (nameToken.startsWith(lowerQuery)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] contentLines = content.split("\n");
|
||||||
|
|
||||||
|
// Locate the lines of the content that contain the query term.
|
||||||
|
for (String contentLine : contentLines) {
|
||||||
|
if (contentLine.toLowerCase().contains(lowerQuery)) {
|
||||||
|
|
||||||
|
// Line contains the query string - now search for it at the start of tokens.
|
||||||
|
List<String> lineTokens = new ArrayList<String>();
|
||||||
|
List<Integer> tokenOffsets = new ArrayList<Integer>();
|
||||||
|
split(contentLine.trim(), lineTokens, tokenOffsets);
|
||||||
|
|
||||||
|
// As we find matches against the query, we'll populate this list with the marked
|
||||||
|
// (or unchanged) tokens.
|
||||||
|
List<String> markedTokens = new ArrayList<String>();
|
||||||
|
|
||||||
|
int firstToken = -1;
|
||||||
|
int lastToken = -1;
|
||||||
|
for (int i = 0; i < lineTokens.size(); i++) {
|
||||||
|
String token = lineTokens.get(i);
|
||||||
|
String lowerToken = token.toLowerCase();
|
||||||
|
if (lowerToken.startsWith(lowerQuery)) {
|
||||||
|
|
||||||
|
// Query term matched; surround the token with match markers.
|
||||||
|
markedTokens.add(snippetStartMatch + token + snippetEndMatch);
|
||||||
|
|
||||||
|
// If this is the first token found with a match, mark the token
|
||||||
|
// positions to use for assembling the snippet.
|
||||||
|
if (firstToken == -1) {
|
||||||
|
firstToken =
|
||||||
|
Math.max(0, i - (int) Math.floor(
|
||||||
|
Math.abs(snippetMaxTokens)
|
||||||
|
/ 2.0));
|
||||||
|
lastToken =
|
||||||
|
Math.min(lineTokens.size(), firstToken +
|
||||||
|
Math.abs(snippetMaxTokens));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
markedTokens.add(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble the snippet by piecing the tokens back together.
|
||||||
|
if (firstToken > -1) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (firstToken > 0) {
|
||||||
|
sb.append(snippetEllipsis);
|
||||||
|
}
|
||||||
|
for (int i = firstToken; i < lastToken; i++) {
|
||||||
|
String markedToken = markedTokens.get(i);
|
||||||
|
String originalToken = lineTokens.get(i);
|
||||||
|
sb.append(markedToken);
|
||||||
|
if (i < lastToken - 1) {
|
||||||
|
// Add the characters that appeared between this token and the next.
|
||||||
|
sb.append(contentLine.substring(
|
||||||
|
tokenOffsets.get(i) + originalToken.length(),
|
||||||
|
tokenOffsets.get(i + 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastToken < lineTokens.size()) {
|
||||||
|
sb.append(snippetEllipsis);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern for splitting a line into tokens. This matches e-mail addresses as a single token,
|
||||||
|
* otherwise splitting on any group of non-alphanumeric characters.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
private static Pattern SPLIT_PATTERN =
|
||||||
|
Pattern.compile("([\\w-\\.]+)@((?:[\\w]+\\.)+)([a-zA-Z]{2,4})|[\\w]+");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for splitting a string into tokens. The lists passed in are populated with the
|
||||||
|
* tokens and offsets into the content of each token. The tokenization function parses e-mail
|
||||||
|
* addresses as a single token; otherwise it splits on any non-alphanumeric character.
|
||||||
|
* @param content Content to split.
|
||||||
|
* @param tokens List of token strings to populate.
|
||||||
|
* @param offsets List of offsets into the content for each token returned.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
private static void split(String content, List<String> tokens, List<Integer> offsets) {
|
||||||
|
Matcher matcher = SPLIT_PATTERN.matcher(content);
|
||||||
|
while (matcher.find()) {
|
||||||
|
tokens.add(matcher.group());
|
||||||
|
offsets.add(matcher.start());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user