Bug 858340 - Domain autocompletion for Fennec. r=jchen

This commit is contained in:
Wes Johnston 2013-04-17 09:13:49 -07:00
parent 9f0bdeec0a
commit 936b1000e7
4 changed files with 172 additions and 34 deletions

View File

@ -46,7 +46,13 @@ import android.widget.Toast;
import java.net.URLEncoder;
public class AwesomeBar extends GeckoActivity {
interface AutocompleteHandler {
void onAutocomplete(String res);
}
public class AwesomeBar extends GeckoActivity
implements AutocompleteHandler,
TextWatcher {
private static final String LOGTAG = "GeckoAwesomeBar";
public static final String URL_KEY = "url";
@ -65,6 +71,10 @@ public class AwesomeBar extends GeckoActivity {
private ContextMenuSubject mContextMenuSubject;
private boolean mIsUsingGestureKeyboard;
private boolean mDelayRestartInput;
// The previous autocomplete result returned to us
private String mAutoCompleteResult = "";
// The user typed part of the autocomplete result
private String mAutoCompletePrefix = null;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -168,35 +178,7 @@ public class AwesomeBar extends GeckoActivity {
}
});
mText.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
String text = s.toString();
mAwesomeTabs.filter(text);
// If the AwesomeBar has a composition string, don't call updateGoButton().
// That method resets IME and composition state will be broken.
if (!hasCompositionString(s)) {
updateGoButton(text);
}
if (Build.VERSION.SDK_INT >= 11) {
getActionBar().hide();
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// do nothing
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
// do nothing
}
});
mText.addTextChangedListener(this);
mText.setOnKeyListener(new View.OnKeyListener() {
@Override
@ -727,4 +709,75 @@ public class AwesomeBar extends GeckoActivity {
}
return false;
}
// return early if we're backspacing through the string, or have no autocomplete results
public void onAutocomplete(final String result) {
final String text = mText.getText().toString();
if (result == null) {
mAutoCompleteResult = "";
return;
}
if (!result.startsWith(text) || text.equals(result)) {
return;
}
mAutoCompleteResult = result;
mText.getText().append(result.substring(text.length()));
mText.setSelection(text.length(), result.length());
}
@Override
public void afterTextChanged(final Editable s) {
final String text = s.toString();
boolean useHandler = false;
boolean reuseAutocomplete = false;
if (!hasCompositionString(s) && !StringUtils.isSearchQuery(text, false)) {
useHandler = true;
// If you're hitting backspace (the string is getting smaller
// or is unchanged), don't autocomplete.
if (mAutoCompletePrefix != null && (mAutoCompletePrefix.length() >= text.length())) {
useHandler = false;
} else if (mAutoCompleteResult != null && mAutoCompleteResult.startsWith(text)) {
// If this text already matches our autocomplete text, autocomplete likely
// won't change. Just reuse the old autocomplete value.
useHandler = false;
reuseAutocomplete = true;
}
}
// If this is the autocomplete text being set, don't run the filter.
if (mAutoCompleteResult == null || !mAutoCompleteResult.equals(text)) {
mAwesomeTabs.filter(text, useHandler ? this : null);
mAutoCompletePrefix = text;
if (reuseAutocomplete) {
onAutocomplete(mAutoCompleteResult);
}
}
// If the AwesomeBar has a composition string, don't call updateGoButton().
// That method resets IME and composition state will be broken.
if (!hasCompositionString(s)) {
updateGoButton(text);
}
if (Build.VERSION.SDK_INT >= 11) {
getActionBar().hide();
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// do nothing
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
// do nothing
}
}

View File

@ -173,7 +173,7 @@ public class AwesomeBarTabs extends TabHost
}
// Initialize "All Pages" list with no filter
filter("");
filter("", null);
}
@Override
@ -308,7 +308,7 @@ public class AwesomeBarTabs extends TabHost
return (HistoryTab)getAwesomeBarTabForTag("history");
}
public void filter(String searchTerm) {
public void filter(String searchTerm, AutocompleteHandler handler) {
// If searching, disable left / right tab swipes
mSearching = searchTerm.length() != 0;
@ -322,7 +322,7 @@ public class AwesomeBarTabs extends TabHost
styleSelectedTab();
// Perform the actual search
allPages.filter(searchTerm);
allPages.filter(searchTerm, handler);
// If searching, hide the tabs bar
findViewById(R.id.tab_widget_container).setVisibility(mSearching ? View.GONE : View.VISIBLE);

View File

@ -25,6 +25,7 @@ import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
@ -63,6 +64,8 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
private static final int SUGGESTION_TIMEOUT = 3000;
private static final int SUGGESTION_MAX = 3;
private static final int ANIMATION_DURATION = 250;
// The maximum number of rows deep in a search we'll dig for an autocomplete result
private static final int MAX_AUTOCOMPLETE_SEARCH = 20;
private String mSearchTerm;
private ArrayList<SearchEngine> mSearchEngines;
@ -76,6 +79,7 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
private View mSuggestionsOptInPrompt;
private Handler mHandler;
private ListView mListView;
private volatile AutocompleteHandler mAutocompleteHandler = null;
private static final int MESSAGE_LOAD_FAVICONS = 1;
private static final int MESSAGE_UPDATE_FAVICONS = 2;
@ -169,7 +173,9 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
}
}
public void filter(String searchTerm) {
public void filter(String searchTerm, AutocompleteHandler handler) {
mAutocompleteHandler = handler;
AwesomeBarCursorAdapter adapter = getCursorAdapter();
adapter.filter(searchTerm);
@ -182,6 +188,61 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
}
}
private void findAutocompleteFor(String searchTerm, Cursor cursor) {
if (TextUtils.isEmpty(searchTerm) || cursor == null || mAutocompleteHandler == null)
return;
// avoid searching the path if we don't have to. Currently just decided by if there is
// a '/' character in the string
final String res = searchHosts(searchTerm, cursor, searchTerm.indexOf("/") > 0);
if (res != null) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
// Its possible that mAutocompleteHandler has been destroyed
if (mAutocompleteHandler != null) {
mAutocompleteHandler.onAutocomplete(res);
mAutocompleteHandler = null;
}
}
});
}
}
private String searchHosts(String searchTerm, Cursor cursor, boolean searchPath) {
int i = 0;
if (cursor.moveToFirst()) {
int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL);
do {
final Uri url = Uri.parse(cursor.getString(urlIndex));
String host = StringUtils.stripCommonSubdomains(url.getHost());
// host may be null for about pages
if (host == null)
continue;
StringBuilder hostBuilder = new StringBuilder(host);
if (hostBuilder.indexOf(searchTerm) == 0) {
return hostBuilder.append("/").toString();
}
if (searchPath) {
List<String> path = url.getPathSegments();
for (String seg : path) {
hostBuilder.append("/").append(seg);
if (hostBuilder.indexOf(searchTerm) == 0) {
return hostBuilder.append("/").toString();
}
}
}
i++;
} while (i < MAX_AUTOCOMPLETE_SEARCH && cursor.moveToNext());
}
return null;
}
/**
* Query for suggestions, but don't show them yet.
*/
@ -237,6 +298,8 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
Telemetry.HistogramAdd("FENNEC_AWESOMEBAR_ALLPAGES_EMPTY_TIME", time);
mTelemetrySent = true;
}
findAutocompleteFor(constraint.toString(), c);
return c;
}
});

View File

@ -44,4 +44,26 @@ public class StringUtils {
// Otherwise, text is ambiguous, and we keep its status unchanged
return wasSearchQuery;
}
public static String stripScheme(String url) {
if (url == null)
return url;
if (url.startsWith("http://")) {
return url.substring(7);
}
return url;
}
public static String stripCommonSubdomains(String host) {
if (host == null)
return host;
// In contrast to desktop, we also strip mobile subdomains,
// since its unlikely users are intentionally typing them
if (host.startsWith("www.")) return host.substring(4);
else if (host.startsWith("mobile.")) return host.substring(7);
else if (host.startsWith("m.")) return host.substring(2);
return host;
}
}