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.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
@@ -44,6 +45,9 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -166,6 +170,22 @@ public final class ContactsContract {
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@@ -4857,6 +4877,19 @@ public final class ContactsContract {
|
||||
* @hide
|
||||
*/
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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