author | Lucas Rocha <lucasr@mozilla.com> |
Tue, 11 Jun 2013 17:57:45 +0100 | |
changeset 143319 | 68cd82adf04026606f618ded50d82b160eb13fa1 |
parent 143318 | 0807f3d71b68b3ed1b03bcd2b2a67feb298164f1 |
child 143320 | 72e003d38d009b4630f9cec45bc98e70f2d5767c |
push id | 25130 |
push user | lrocha@mozilla.com |
push date | Wed, 21 Aug 2013 09:41:27 +0000 |
treeherder | mozilla-central@b2486721572e [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bnicholson |
bugs | 877870 |
milestone | 24.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -69,31 +69,36 @@ import java.net.URL; import java.util.EnumSet; import java.util.Vector; abstract public class BrowserApp extends GeckoApp implements TabsPanel.TabsLayoutChangeListener, PropertyAnimator.PropertyAnimationListener, View.OnKeyListener, GeckoLayerClient.OnMetricsChangedListener, + BrowserSearch.OnUrlOpenListener, HomePager.OnUrlOpenListener { private static final String LOGTAG = "GeckoBrowserApp"; private static final String PREF_CHROME_DYNAMICTOOLBAR = "browser.chrome.dynamictoolbar"; private static final String ABOUT_HOME = "about:home"; private static final int TABS_ANIMATION_DURATION = 450; private static final int READER_ADD_SUCCESS = 0; private static final int READER_ADD_FAILED = 1; private static final int READER_ADD_DUPLICATE = 2; 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; private static final int ADDON_MENU_OFFSET = 1000; private class MenuItemInfo { public int id; public String label; @@ -376,16 +381,23 @@ abstract public class BrowserApp extends } } return false; } }); 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); mBrowserToolbar.setOnActivateListener(new BrowserToolbar.OnActivateListener() { public void onActivate() { enterEditingMode(EditingTarget.CURRENT_TAB); } }); @@ -1120,16 +1132,18 @@ abstract public class BrowserApp extends mBrowserToolbar.setProgressVisibility(true); int flags = Tabs.LOADURL_NONE; if (mBrowserToolbar.getEditingTarget() == EditingTarget.NEW_TAB) { flags |= Tabs.LOADURL_NEW_TAB; } Tabs.getInstance().loadUrl(url, flags); + + hideBrowserSearch(); mBrowserToolbar.cancelEdit(); } /* Favicon methods */ private void loadFavicon(final Tab tab) { maybeCancelFaviconLoad(tab); long id = Favicons.getInstance().loadFavicon(tab.getURL(), tab.getFaviconURL(), !tab.isPrivate(), @@ -1202,16 +1216,17 @@ abstract public class BrowserApp extends void commitEditingMode(EditingTarget target) { if (!mBrowserToolbar.isEditing()) { return; } final String url = mBrowserToolbar.commitEdit(); animateHideHomePager(); + hideBrowserSearch(); int flags = Tabs.LOADURL_USER_ENTERED; if (target == EditingTarget.NEW_TAB) { flags |= Tabs.LOADURL_NEW_TAB; } if (!TextUtils.isEmpty(url)) { Tabs.getInstance().loadUrl(url, flags); @@ -1220,22 +1235,28 @@ abstract public class BrowserApp extends boolean dismissEditingMode() { if (!mBrowserToolbar.isEditing()) { return false; } 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() { showHomePagerWithAnimation(true); } private void showHomePager() { showHomePagerWithAnimation(false); @@ -1284,16 +1305,40 @@ abstract public class BrowserApp extends mBrowserToolbar.setShadowVisibility(true); mBrowserToolbar.setNextFocusDownId(R.id.layer_view); // Refresh toolbar height to possibly restore the toolbar padding 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; @Override public boolean onInterceptTouchEvent(View view, MotionEvent event) { // We need to account for scroll state for the touched view otherwise // tapping on an "empty" part of the view will still be considered a // valid touch event. @@ -1768,17 +1813,17 @@ abstract public class BrowserApp extends public void onPostExecute(String url) { // Don't bother sending a message if there is no URL. if (url.length() > 0) GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feedback:LastUrl", url)); } }).execute(); } - // HomePager.OnUrlOpenListener + // (HomePager|BrowserSearch).OnUrlOpenListener @Override public void onUrlOpen(String url) { openUrl(url); } @Override public int getLayout() { return R.layout.gecko_app; }
new file mode 100644 --- /dev/null +++ b/mobile/android/base/BrowserSearch.java @@ -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; + } + } +} \ No newline at end of file
--- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -61,16 +61,17 @@ FENNEC_JAVA_FILES = \ animation/Rotate3DAnimation.java \ animation/ViewHelper.java \ awesomebar/AwesomeBarTab.java \ awesomebar/AllPagesTab.java \ awesomebar/BookmarksTab.java \ awesomebar/HistoryTab.java \ BackButton.java \ BrowserApp.java \ + BrowserSearch.java \ BrowserToolbar.java \ BrowserToolbarBackground.java \ BrowserToolbarLayout.java \ CameraImageResultHandler.java \ CameraVideoResultHandler.java \ CanvasDelegate.java \ CheckableLinearLayout.java \ ClickableWhenDisabledEditText.java \ @@ -143,16 +144,17 @@ FENNEC_JAVA_FILES = \ ReaderModeUtils.java \ RemoteTabs.java \ RobocopAPI.java \ ServiceNotificationClient.java \ SessionParser.java \ SetupScreen.java \ ShapedButton.java \ SharedPreferencesHelper.java \ + SimpleCursorLoader.java \ SiteIdentityPopup.java \ SmsManager.java \ SuggestClient.java \ SurfaceBits.java \ SyncPreference.java \ Tab.java \ TabCounter.java \ Tabs.java \
new file mode 100644 --- /dev/null +++ b/mobile/android/base/SimpleCursorLoader.java @@ -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; + } +} \ No newline at end of file
deleted file mode 100644 --- a/mobile/android/base/resources/layout/bookmark_item_row.xml +++ /dev/null @@ -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"/>
--- a/mobile/android/base/resources/layout/gecko_app.xml +++ b/mobile/android/base/resources/layout/gecko_app.xml @@ -50,11 +50,19 @@ android:layout_height="wrap_content" android:layout_alignParentBottom="true" style="@style/FindBar" android:visibility="gone"/> <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>