Bug 877870 - Initial implementation of new browser search fragment (r=bnicholson)

This commit is contained in:
Lucas Rocha 2013-06-11 17:57:45 +01:00
parent 6c792631d5
commit 0b49d16137
6 changed files with 390 additions and 11 deletions

View File

@ -74,6 +74,7 @@ abstract public class BrowserApp extends GeckoApp
PropertyAnimator.PropertyAnimationListener,
View.OnKeyListener,
GeckoLayerClient.OnMetricsChangedListener,
BrowserSearch.OnUrlOpenListener,
HomePager.OnUrlOpenListener {
private static final String LOGTAG = "GeckoBrowserApp";
@ -89,6 +90,10 @@ abstract public class BrowserApp extends GeckoApp
private static final String STATE_DYNAMIC_TOOLBAR_ENABLED = "dynamic_toolbar";
private static final String BROWSER_SEARCH_TAG = "browser_search";
private BrowserSearch mBrowserSearch;
private View mBrowserSearchContainer;
public static BrowserToolbar mBrowserToolbar;
private HomePager mHomePager;
protected Telemetry.Timer mAboutHomeStartupTimer = null;
@ -381,6 +386,13 @@ abstract public class BrowserApp extends GeckoApp
mHomePager = (HomePager) findViewById(R.id.home_pager);
mBrowserSearchContainer = findViewById(R.id.search_container);
mBrowserSearch = (BrowserSearch) getSupportFragmentManager().findFragmentByTag(BROWSER_SEARCH_TAG);
if (mBrowserSearch == null) {
mBrowserSearch = BrowserSearch.newInstance();
mBrowserSearch.setUserVisibleHint(false);
}
mBrowserToolbar = new BrowserToolbar(this);
mBrowserToolbar.from(actionBar);
@ -1125,6 +1137,8 @@ abstract public class BrowserApp extends GeckoApp
}
Tabs.getInstance().loadUrl(url, flags);
hideBrowserSearch();
mBrowserToolbar.cancelEdit();
}
@ -1207,6 +1221,7 @@ abstract public class BrowserApp extends GeckoApp
final String url = mBrowserToolbar.commitEdit();
animateHideHomePager();
hideBrowserSearch();
int flags = Tabs.LOADURL_USER_ENTERED;
if (target == EditingTarget.NEW_TAB) {
@ -1225,12 +1240,18 @@ abstract public class BrowserApp extends GeckoApp
mBrowserToolbar.cancelEdit();
animateHideHomePager();
hideBrowserSearch();
return true;
}
void filterEditingMode(String searchTerm, AutocompleteHandler handler) {
// FIXME: implement actual awesomebar search
if (TextUtils.isEmpty(searchTerm)) {
hideBrowserSearch();
} else {
showBrowserSearch();
mBrowserSearch.filter(searchTerm);
}
}
private void animateShowHomePager() {
@ -1289,6 +1310,30 @@ abstract public class BrowserApp extends GeckoApp
refreshToolbarHeight();
}
private void showBrowserSearch() {
if (mBrowserSearch.getUserVisibleHint()) {
return;
}
mBrowserSearchContainer.setVisibility(View.VISIBLE);
getSupportFragmentManager().beginTransaction()
.add(R.id.search_container, mBrowserSearch, BROWSER_SEARCH_TAG).commitAllowingStateLoss();
mBrowserSearch.setUserVisibleHint(true);
}
private void hideBrowserSearch() {
if (!mBrowserSearch.getUserVisibleHint()) {
return;
}
mBrowserSearchContainer.setVisibility(View.INVISIBLE);
getSupportFragmentManager().beginTransaction()
.remove(mBrowserSearch).commitAllowingStateLoss();
mBrowserSearch.setUserVisibleHint(false);
}
private class HideTabsTouchListener implements TouchEventInterceptor {
private boolean mIsHidingTabs = false;
@ -1773,7 +1818,7 @@ abstract public class BrowserApp extends GeckoApp
}).execute();
}
// HomePager.OnUrlOpenListener
// (HomePager|BrowserSearch).OnUrlOpenListener
@Override
public void onUrlOpen(String url) {
openUrl(url);

View File

@ -0,0 +1,202 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.home.TwoLinePageRow;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.content.res.Configuration;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
/**
* Fragment that displays frecency search results in a ListView.
*/
public class BrowserSearch extends Fragment implements LoaderCallbacks<Cursor>,
AdapterView.OnItemClickListener {
// Cursor loader ID for search query
private static final int SEARCH_LOADER_ID = 0;
// Holds the current search term to use in the query
private String mSearchTerm;
// Adapter for the list of search results
private SearchAdapter mAdapter;
// The view shown by the fragment.
private ListView mList;
// On URL open listener
private OnUrlOpenListener mUrlOpenListener;
public interface OnUrlOpenListener {
public void onUrlOpen(String url);
}
public static BrowserSearch newInstance() {
return new BrowserSearch();
}
public BrowserSearch() {
mSearchTerm = "";
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mUrlOpenListener = (OnUrlOpenListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement BrowserSearch.OnUrlOpenListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mUrlOpenListener = null;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// All list views are styled to look the same with a global activity theme.
// If the style of the list changes, inflate it from an XML.
mList = new ListView(container.getContext());
return mList;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mList.setOnItemClickListener(this);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Intialize the search adapter
mAdapter = new SearchAdapter(getActivity());
mList.setAdapter(mAdapter);
// Reconnect to the loader only if present
getLoaderManager().initLoader(SEARCH_LOADER_ID, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new SearchCursorLoader(getActivity(), mSearchTerm);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
mAdapter.swapCursor(c);
// FIXME: do extra UI bits here
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Cursor c = mAdapter.getCursor();
if (c == null || !c.moveToPosition(position)) {
return;
}
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
mUrlOpenListener.onUrlOpen(url);
}
public void filter(String searchTerm) {
if (TextUtils.isEmpty(searchTerm)) {
return;
}
if (TextUtils.equals(mSearchTerm, searchTerm)) {
return;
}
mSearchTerm = searchTerm;
if (isVisible()) {
getLoaderManager().restartLoader(SEARCH_LOADER_ID, null, this);
}
}
private static class SearchCursorLoader extends SimpleCursorLoader {
// Max number of search results
private static final int SEARCH_LIMIT = 100;
// The target search term associated with the loader
private final String mSearchTerm;
public SearchCursorLoader(Context context, String searchTerm) {
super(context);
mSearchTerm = searchTerm;
}
@Override
public Cursor loadCursor() {
if (TextUtils.isEmpty(mSearchTerm)) {
return null;
}
final ContentResolver cr = getContext().getContentResolver();
return BrowserDB.filter(cr, mSearchTerm, SEARCH_LIMIT);
}
}
private class SearchAdapter extends SimpleCursorAdapter {
public SearchAdapter(Context context) {
super(context, -1, null, new String[] {}, new int[] {});
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final TwoLinePageRow row;
if (convertView == null) {
row = (TwoLinePageRow) LayoutInflater.from(getActivity()).inflate(R.layout.home_item_row, null);
} else {
row = (TwoLinePageRow) convertView;
}
final Cursor c = getCursor();
if (!c.moveToPosition(position)) {
throw new IllegalStateException("Couldn't move cursor to position " + position);
}
row.updateFromCursor(c);
// FIXME: show bookmark icon
return row;
}
}
}

View File

@ -66,6 +66,7 @@ FENNEC_JAVA_FILES = \
awesomebar/HistoryTab.java \
BackButton.java \
BrowserApp.java \
BrowserSearch.java \
BrowserToolbar.java \
BrowserToolbarBackground.java \
BrowserToolbarLayout.java \
@ -148,6 +149,7 @@ FENNEC_JAVA_FILES = \
SetupScreen.java \
ShapedButton.java \
SharedPreferencesHelper.java \
SimpleCursorLoader.java \
SiteIdentityPopup.java \
SmsManager.java \
SuggestClient.java \

View File

@ -0,0 +1,131 @@
/*
* This is an adapted version of Android's original CursorLoader
* without all the ContentProvider-specific bits.
*
* Copyright (C) 2011 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 org.mozilla.gecko;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.support.v4.content.AsyncTaskLoader;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
public abstract class SimpleCursorLoader extends AsyncTaskLoader<Cursor> {
final ForceLoadContentObserver mObserver;
Cursor mCursor;
public SimpleCursorLoader(Context context) {
super(context);
mObserver = new ForceLoadContentObserver();
}
/**
* Loads the target cursor for this loader. This method is called
* on a worker thread.
*/
protected abstract Cursor loadCursor();
/* Runs on a worker thread */
@Override
public Cursor loadInBackground() {
Cursor cursor = loadCursor();
if (cursor != null) {
// Ensure the cursor window is filled
cursor.getCount();
cursor.registerContentObserver(mObserver);
}
return cursor;
}
/* Runs on the UI thread */
@Override
public void deliverResult(Cursor cursor) {
if (isReset()) {
// An async query came in while the loader is stopped
if (cursor != null) {
cursor.close();
}
return;
}
Cursor oldCursor = mCursor;
mCursor = cursor;
if (isStarted()) {
super.deliverResult(cursor);
}
if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
oldCursor.close();
}
}
/**
* Starts an asynchronous load of the list data. When the result is ready the callbacks
* will be called on the UI thread. If a previous load has been completed and is still valid
* the result may be passed to the callbacks immediately.
*
* Must be called from the UI thread
*/
@Override
protected void onStartLoading() {
if (mCursor != null) {
deliverResult(mCursor);
}
if (takeContentChanged() || mCursor == null) {
forceLoad();
}
}
/**
* Must be called from the UI thread
*/
@Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
@Override
public void onCanceled(Cursor cursor) {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
if (mCursor != null && !mCursor.isClosed()) {
mCursor.close();
}
mCursor = null;
}
}

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<org.mozilla.gecko.home.TwoLinePageRow xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="@dimen/page_row_height"
android:minHeight="@dimen/page_row_height"/>

View File

@ -55,6 +55,14 @@
<include layout="@layout/browser_toolbar"
android:layout_width="fill_parent"
android:layout_height="@dimen/browser_toolbar_height"/>
<FrameLayout android:id="@+id/search_container"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="@id/browser_toolbar"
android:background="@android:color/white"
android:visibility="invisible"/>
</view>
</RelativeLayout>