rename from mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
rename to mobile/android/app/src/australis/java/org/mozilla/gecko/home/SearchEngineRow.java
rename from mobile/android/app/src/main/res/drawable/search_suggestion_button.xml
rename to mobile/android/app/src/australis/res/drawable/search_suggestion_button.xml
rename from mobile/android/app/src/main/res/drawable/search_suggestion_prompt_no.xml
rename to mobile/android/app/src/australis/res/drawable/search_suggestion_prompt_no.xml
rename from mobile/android/app/src/main/res/drawable/search_suggestion_prompt_yes.xml
rename to mobile/android/app/src/australis/res/drawable/search_suggestion_prompt_yes.xml
rename from mobile/android/app/src/main/res/layout/browser_search.xml
rename to mobile/android/app/src/australis/res/layout/browser_search.xml
rename from mobile/android/app/src/main/res/layout/home_item_row.xml
rename to mobile/android/app/src/australis/res/layout/home_item_row.xml
rename from mobile/android/app/src/main/res/layout/home_search_item_row.xml
rename to mobile/android/app/src/australis/res/layout/home_search_item_row.xml
rename from mobile/android/app/src/main/res/layout/home_suggestion_prompt.xml
rename to mobile/android/app/src/australis/res/layout/home_suggestion_prompt.xml
rename from mobile/android/app/src/main/res/layout/search_engine_row.xml
rename to mobile/android/app/src/australis/res/layout/search_engine_row.xml
rename from mobile/android/app/src/main/res/layout/suggestion_item.xml
rename to mobile/android/app/src/australis/res/layout/suggestion_item.xml
rename from mobile/android/app/src/main/res/layout/two_line_page_row.xml
rename to mobile/android/app/src/australis/res/layout/two_line_page_row.xml
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/java/org/mozilla/gecko/home/SearchEngineRow.java
@@ -0,0 +1,483 @@
+/* -*- 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.home;
+
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.home.BrowserSearch.OnEditSuggestionListener;
+import org.mozilla.gecko.home.BrowserSearch.OnSearchListener;
+import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
+import org.mozilla.gecko.icons.IconResponse;
+import org.mozilla.gecko.preferences.GeckoPreferences;
+import org.mozilla.gecko.util.DrawableUtil;
+import org.mozilla.gecko.util.StringUtils;
+import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.widget.FaviconView;
+import org.mozilla.gecko.widget.FlowLayout;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.drawable.Drawable;
+import android.graphics.Typeface;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+class SearchEngineRow extends RelativeLayout {
+
+ // Inner views
+ private final FlowLayout mSuggestionView;
+ private final FaviconView mIconView;
+ private final LinearLayout mUserEnteredView;
+ private final TextView mUserEnteredTextView;
+
+ // Inflater used when updating from suggestions
+ private final LayoutInflater mInflater;
+
+ // Search engine associated with this view
+ private SearchEngine mSearchEngine;
+
+ // Event listeners for suggestion views
+ private final OnClickListener mClickListener;
+ private final OnLongClickListener mLongClickListener;
+
+ // On URL open listener
+ private OnUrlOpenListener mUrlOpenListener;
+
+ // On search listener
+ private OnSearchListener mSearchListener;
+
+ // On edit suggestion listener
+ private OnEditSuggestionListener mEditSuggestionListener;
+
+ // Selected suggestion view
+ private int mSelectedView;
+
+ // android:backgroundTint only works in Android 21 and higher so we can't do this statically in the xml
+ private Drawable mSearchHistorySuggestionIcon;
+
+ // Maximums for suggestions
+ private int mMaxSavedSuggestions;
+ private int mMaxSearchSuggestions;
+
+ private final List<Integer> mOccurrences = new ArrayList<>();
+
+ public SearchEngineRow(Context context) {
+ this(context, null);
+ }
+
+ public SearchEngineRow(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SearchEngineRow(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final String suggestion = getSuggestionTextFromView(v);
+
+ // If we're not clicking the user-entered view (the first suggestion item)
+ // and the search matches a URL pattern, go to that URL. Otherwise, do a
+ // search for the term.
+ if (v != mUserEnteredView && !StringUtils.isSearchQuery(suggestion, true)) {
+ if (mUrlOpenListener != null) {
+ Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "url");
+
+ mUrlOpenListener.onUrlOpen(suggestion, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
+ }
+ } else if (mSearchListener != null) {
+ if (v == mUserEnteredView) {
+ Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user");
+ } else {
+ Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, (String) v.getTag());
+ }
+ mSearchListener.onSearch(mSearchEngine, suggestion, TelemetryContract.Method.SUGGESTION);
+ }
+ }
+ };
+
+ mLongClickListener = new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ if (mEditSuggestionListener != null) {
+ final String suggestion = getSuggestionTextFromView(v);
+ mEditSuggestionListener.onEditSuggestion(suggestion);
+ return true;
+ }
+
+ return false;
+ }
+ };
+
+ mInflater = LayoutInflater.from(context);
+ mInflater.inflate(R.layout.search_engine_row, this);
+
+ mSuggestionView = (FlowLayout) findViewById(R.id.suggestion_layout);
+ mIconView = (FaviconView) findViewById(R.id.suggestion_icon);
+
+ // User-entered search term is first suggestion
+ mUserEnteredView = (LinearLayout) findViewById(R.id.suggestion_user_entered);
+ mUserEnteredView.setOnClickListener(mClickListener);
+
+ mUserEnteredTextView = (TextView) findViewById(R.id.suggestion_text);
+ mSearchHistorySuggestionIcon = DrawableUtil.tintDrawableWithColorRes(getContext(), R.drawable.icon_most_recent_empty, R.color.tabs_tray_icon_grey);
+
+ // Suggestion limits
+ mMaxSavedSuggestions = getResources().getInteger(R.integer.max_saved_suggestions);
+ mMaxSearchSuggestions = getResources().getInteger(R.integer.max_search_suggestions);
+ }
+
+ private void setDescriptionOnSuggestion(View v, String suggestion) {
+ v.setContentDescription(getResources().getString(R.string.suggestion_for_engine,
+ mSearchEngine.name, suggestion));
+ }
+
+ private String getSuggestionTextFromView(View v) {
+ final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text);
+ return suggestionText.getText().toString();
+ }
+
+ /**
+ * Finds all occurrences of pattern in string and returns a list of the starting indices
+ * of each occurrence.
+ *
+ * @param pattern The pattern that is searched for
+ * @param string The string where we search for the pattern
+ */
+ private void refreshOccurrencesWith(String pattern, String string) {
+ mOccurrences.clear();
+
+ // Don't try to search for an empty string - String.indexOf will return 0, which would result
+ // in us iterating with lastIndexOfMatch = 0, which eventually results in an OOM.
+ if (TextUtils.isEmpty(pattern)) {
+ return;
+ }
+
+ final int patternLength = pattern.length();
+
+ int indexOfMatch = 0;
+ int lastIndexOfMatch = 0;
+ while (indexOfMatch != -1) {
+ indexOfMatch = string.indexOf(pattern, lastIndexOfMatch);
+ lastIndexOfMatch = indexOfMatch + patternLength;
+ if (indexOfMatch != -1) {
+ mOccurrences.add(indexOfMatch);
+ }
+ }
+ }
+
+ /**
+ * Sets the content for the suggestion view.
+ *
+ * If the suggestion doesn't contain mUserSearchTerm, nothing is made bold.
+ * All instances of mUserSearchTerm in the suggestion are not bold.
+ *
+ * @param v The View that needs to be populated
+ * @param suggestion The suggestion text that will be placed in the view
+ * @param isUserSavedSearch whether the suggestion is from history or not
+ */
+ private void setSuggestionOnView(View v, String suggestion, boolean isUserSavedSearch) {
+ final ImageView historyIcon = (ImageView) v.findViewById(R.id.suggestion_item_icon);
+ if (isUserSavedSearch) {
+ historyIcon.setImageDrawable(mSearchHistorySuggestionIcon);
+ historyIcon.setVisibility(View.VISIBLE);
+ } else {
+ historyIcon.setVisibility(View.GONE);
+ }
+
+ final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text);
+ final String searchTerm = getSuggestionTextFromView(mUserEnteredView);
+ final int searchTermLength = searchTerm.length();
+ refreshOccurrencesWith(searchTerm, suggestion);
+ if (mOccurrences.size() > 0) {
+ final SpannableStringBuilder sb = new SpannableStringBuilder(suggestion);
+ int nextStartSpanIndex = 0;
+ // Done to make sure that the stretch of text after the last occurrence, till the end of the suggestion, is made bold
+ mOccurrences.add(suggestion.length());
+ for (int occurrence : mOccurrences) {
+ // Even though they're the same style, SpannableStringBuilder will interpret there as being only one Span present if we re-use a StyleSpan
+ StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
+ sb.setSpan(boldSpan, nextStartSpanIndex, occurrence, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ nextStartSpanIndex = occurrence + searchTermLength;
+ }
+ mOccurrences.clear();
+ suggestionText.setText(sb);
+ } else {
+ suggestionText.setText(suggestion);
+ }
+
+ setDescriptionOnSuggestion(suggestionText, suggestion);
+ }
+
+ /**
+ * Perform a search for the user-entered term.
+ */
+ public void performUserEnteredSearch() {
+ String searchTerm = getSuggestionTextFromView(mUserEnteredView);
+ if (mSearchListener != null) {
+ Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user");
+ mSearchListener.onSearch(mSearchEngine, searchTerm, TelemetryContract.Method.SUGGESTION);
+ }
+ }
+
+ public void setSearchTerm(String searchTerm) {
+ mUserEnteredTextView.setText(searchTerm);
+
+ // mSearchEngine is not set in the first call to this method; the content description
+ // is instead initially set in updateSuggestions().
+ if (mSearchEngine != null) {
+ setDescriptionOnSuggestion(mUserEnteredTextView, searchTerm);
+ }
+ }
+
+ public void setOnUrlOpenListener(OnUrlOpenListener listener) {
+ mUrlOpenListener = listener;
+ }
+
+ public void setOnSearchListener(OnSearchListener listener) {
+ mSearchListener = listener;
+ }
+
+ public void setOnEditSuggestionListener(OnEditSuggestionListener listener) {
+ mEditSuggestionListener = listener;
+ }
+
+ private void bindSuggestionView(String suggestion, int recycledSuggestionCount, Integer previousSuggestionChildIndex, boolean isUserSavedSearch, String telemetryTag) {
+ final View suggestionItem;
+
+ // Reuse suggestion views from recycled view, if possible.
+ if (previousSuggestionChildIndex + 1 < recycledSuggestionCount) {
+ suggestionItem = mSuggestionView.getChildAt(previousSuggestionChildIndex + 1);
+ suggestionItem.setVisibility(View.VISIBLE);
+ } else {
+ suggestionItem = mInflater.inflate(R.layout.suggestion_item, null);
+
+ suggestionItem.setOnClickListener(mClickListener);
+ suggestionItem.setOnLongClickListener(mLongClickListener);
+
+ suggestionItem.setTag(telemetryTag);
+
+ mSuggestionView.addView(suggestionItem);
+ }
+
+ setSuggestionOnView(suggestionItem, suggestion, isUserSavedSearch);
+ }
+
+ private void hideRecycledSuggestions(int lastVisibleChildIndex, int recycledSuggestionCount) {
+ // Hide extra suggestions that have been recycled.
+ for (int i = lastVisibleChildIndex + 1; i < recycledSuggestionCount; ++i) {
+ mSuggestionView.getChildAt(i).setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Displays search suggestions from previous searches.
+ *
+ * @param savedSuggestions The List to iterate over for saved search suggestions to display. This function does not
+ * enforce a ui maximum or filter. It will show all the suggestions in this list.
+ * @param suggestionStartIndex global index of where to start adding suggestion "buttons" in the search engine row. Also
+ * acts as a counter for total number of suggestions visible.
+ * @param recycledSuggestionCount How many suggestion "button" views we could recycle from previous calls
+ */
+ private void updateFromSavedSearches(List<String> savedSuggestions, int suggestionStartIndex, int recycledSuggestionCount) {
+ if (savedSuggestions == null || savedSuggestions.isEmpty()) {
+ hideRecycledSuggestions(suggestionStartIndex, recycledSuggestionCount);
+ return;
+ }
+
+ final int numSavedSearches = savedSuggestions.size();
+ int indexOfPreviousSuggestion = 0;
+ for (int i = 0; i < numSavedSearches; i++) {
+ String telemetryTag = "history." + i;
+ final String suggestion = savedSuggestions.get(i);
+ indexOfPreviousSuggestion = suggestionStartIndex + i;
+ bindSuggestionView(suggestion, recycledSuggestionCount, indexOfPreviousSuggestion, true, telemetryTag);
+ }
+
+ hideRecycledSuggestions(indexOfPreviousSuggestion + 1, recycledSuggestionCount);
+ }
+
+ /**
+ * Displays suggestions supplied by the search engine, relative to number of suggestions from search history.
+ *
+ * @param recycledSuggestionCount How many suggestion "button" views we could recycle from previous calls
+ * @param savedSuggestionCount how many saved searches this searchTerm has
+ * @return the global count of how many suggestions have been bound/shown in the search engine row
+ */
+ private int updateFromSearchEngine(List<String> searchEngineSuggestions, int recycledSuggestionCount, int savedSuggestionCount) {
+ int maxSuggestions = mMaxSearchSuggestions;
+ // If there are less than max saved searches on phones, fill the space with more search engine suggestions
+ if (!HardwareUtils.isTablet() && savedSuggestionCount < mMaxSavedSuggestions) {
+ maxSuggestions += mMaxSavedSuggestions - savedSuggestionCount;
+ }
+
+ final int numSearchEngineSuggestions = searchEngineSuggestions.size();
+ int relativeIndex;
+ for (relativeIndex = 0; relativeIndex < numSearchEngineSuggestions; relativeIndex++) {
+ if (relativeIndex == maxSuggestions) {
+ break;
+ }
+
+ // Since the search engine suggestions are listed first, their relative index is their global index
+ String telemetryTag = "engine." + relativeIndex;
+ final String suggestion = searchEngineSuggestions.get(relativeIndex);
+ bindSuggestionView(suggestion, recycledSuggestionCount, relativeIndex, false, telemetryTag);
+ }
+
+ hideRecycledSuggestions(relativeIndex + 1, recycledSuggestionCount);
+
+ // Make sure mSelectedView is still valid.
+ if (mSelectedView >= mSuggestionView.getChildCount()) {
+ mSelectedView = mSuggestionView.getChildCount() - 1;
+ }
+
+ return relativeIndex;
+ }
+
+ /**
+ * Updates the whole suggestions UI, the search engine UI, suggestions from the default search engine,
+ * and suggestions from search history.
+ *
+ * This can be called before the opt-in permission prompt is shown or set.
+ * Even if both suggestion types are disabled, we need to update the search engine, its image, and the content description.
+ *
+ * @param searchSuggestionsEnabled whether or not suggestions from the default search engine are enabled
+ * @param searchEngine the search engine to use throughout the SearchEngineRow class
+ * @param rawSearchHistorySuggestions search history suggestions
+ **/
+ public void updateSuggestions(boolean searchSuggestionsEnabled, SearchEngine searchEngine, @Nullable List<String> rawSearchHistorySuggestions) {
+ mSearchEngine = searchEngine;
+ // Set the search engine icon (e.g., Google) for the row.
+
+ mIconView.updateAndScaleImage(IconResponse.create(mSearchEngine.getIcon()));
+ // Set the initial content description.
+ setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString());
+
+ final int recycledSuggestionCount = mSuggestionView.getChildCount();
+ final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext());
+ final boolean savedSearchesEnabled = prefs.getBoolean(GeckoPreferences.PREFS_HISTORY_SAVED_SEARCH, true);
+
+ // Remove duplicates of search engine suggestions from saved searches.
+ List<String> searchHistorySuggestions = (rawSearchHistorySuggestions != null) ? rawSearchHistorySuggestions : new ArrayList<String>();
+
+ // Filter out URLs and long search suggestions
+ Iterator<String> searchHistoryIterator = searchHistorySuggestions.iterator();
+ while (searchHistoryIterator.hasNext()) {
+ final String currentSearchHistory = searchHistoryIterator.next();
+
+ if (currentSearchHistory.length() > 50 || Pattern.matches("^(https?|ftp|file)://.*", currentSearchHistory)) {
+ searchHistoryIterator.remove();
+ }
+ }
+
+
+ final List<String> searchEngineSuggestions = new ArrayList<>();
+ if (searchSuggestionsEnabled) {
+ for (String suggestion : searchEngine.getSuggestions()) {
+ searchHistorySuggestions.remove(suggestion);
+ searchEngineSuggestions.add(suggestion);
+ }
+ }
+ // Make sure the search term itself isn't duplicated. This is more important on phones than tablets where screen
+ // space is more precious.
+ searchHistorySuggestions.remove(getSuggestionTextFromView(mUserEnteredView));
+
+ // Trim the history suggestions down to the maximum allowed.
+ if (searchHistorySuggestions.size() >= mMaxSavedSuggestions) {
+ // The second index to subList() is exclusive, so this looks like an off by one error but it is not.
+ searchHistorySuggestions = searchHistorySuggestions.subList(0, mMaxSavedSuggestions);
+ }
+ final int searchHistoryCount = searchHistorySuggestions.size();
+
+ if (searchSuggestionsEnabled && savedSearchesEnabled) {
+ final int suggestionViewCount = updateFromSearchEngine(searchEngineSuggestions, recycledSuggestionCount, searchHistoryCount);
+ updateFromSavedSearches(searchHistorySuggestions, suggestionViewCount, recycledSuggestionCount);
+ } else if (savedSearchesEnabled) {
+ updateFromSavedSearches(searchHistorySuggestions, 0, recycledSuggestionCount);
+ } else if (searchSuggestionsEnabled) {
+ updateFromSearchEngine(searchEngineSuggestions, recycledSuggestionCount, 0);
+ } else {
+ // The current search term is treated separately from the suggestions list, hence we can
+ // recycle ALL suggestion items here. (We always show the current search term, i.e. 1 item,
+ // in front of the search engine suggestions and/or the search history.)
+ hideRecycledSuggestions(0, recycledSuggestionCount);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, android.view.KeyEvent event) {
+ final View suggestion = mSuggestionView.getChildAt(mSelectedView);
+
+ if (event.getAction() != android.view.KeyEvent.ACTION_DOWN) {
+ return false;
+ }
+
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ final View nextSuggestion = mSuggestionView.getChildAt(mSelectedView + 1);
+ if (nextSuggestion != null) {
+ changeSelectedSuggestion(suggestion, nextSuggestion);
+ mSelectedView++;
+ return true;
+ }
+ break;
+
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ final View prevSuggestion = mSuggestionView.getChildAt(mSelectedView - 1);
+ if (prevSuggestion != null) {
+ changeSelectedSuggestion(suggestion, prevSuggestion);
+ mSelectedView--;
+ return true;
+ }
+ break;
+
+ case KeyEvent.KEYCODE_BUTTON_A:
+ // TODO: handle long pressing for editing suggestions
+ return suggestion.performClick();
+ }
+
+ return false;
+ }
+
+ private void changeSelectedSuggestion(View oldSuggestion, View newSuggestion) {
+ oldSuggestion.setDuplicateParentStateEnabled(false);
+ newSuggestion.setDuplicateParentStateEnabled(true);
+ oldSuggestion.refreshDrawableState();
+ newSuggestion.refreshDrawableState();
+ }
+
+ public void onSelected() {
+ mSelectedView = 0;
+ mUserEnteredView.setDuplicateParentStateEnabled(true);
+ mUserEnteredView.refreshDrawableState();
+ }
+
+ public void onDeselected() {
+ final View suggestion = mSuggestionView.getChildAt(mSelectedView);
+ suggestion.setDuplicateParentStateEnabled(false);
+ suggestion.refreshDrawableState();
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/drawable/search_suggestion_button.xml
@@ -0,0 +1,19 @@
+<?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/. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true">
+ <shape>
+ <solid android:color="@color/toolbar_grey_pressed"/>
+ <corners android:radius="@dimen/standard_corner_radius"/>
+ </shape>
+ </item>
+
+ <item>
+ <shape>
+ <solid android:color="@color/toolbar_grey"/>
+ <corners android:radius="@dimen/standard_corner_radius"/>
+ </shape>
+ </item>
+</selector>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/drawable/search_suggestion_prompt_no.xml
@@ -0,0 +1,20 @@
+<?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/. -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true">
+ <shape>
+ <solid android:color="@color/toolbar_grey_pressed"/>
+ <corners android:radius="@dimen/doorhanger_rounded_corner_radius"/>
+ </shape>
+ </item>
+
+ <item>
+ <shape>
+ <solid android:color="@color/toolbar_menu_dark_grey"/>
+ <corners android:radius="@dimen/doorhanger_rounded_corner_radius"/>
+ </shape>
+ </item>
+</selector>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/drawable/search_suggestion_prompt_yes.xml
@@ -0,0 +1,20 @@
+<?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/. -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+<item android:state_pressed="true">
+ <shape>
+ <solid android:color="@color/link_blue_pressed"/>
+ <corners android:radius="@dimen/doorhanger_rounded_corner_radius"/>
+ </shape>
+</item>
+
+<item>
+ <shape>
+ <solid android:color="@color/link_blue"/>
+ <corners android:radius="@dimen/doorhanger_rounded_corner_radius"/>
+ </shape>
+</item>
+</selector>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/layout/browser_search.xml
@@ -0,0 +1,35 @@
+<?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/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ViewStub android:id="@+id/suggestions_opt_in_prompt"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout="@layout/home_suggestion_prompt" />
+
+ <view class="org.mozilla.gecko.home.BrowserSearch$HomeSearchListView"
+ android:id="@+id/home_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <!-- listSelector is too slow for showing pressed state
+ so we set the pressed colors on the child. -->
+ <org.mozilla.gecko.home.SearchEngineBar
+ android:id="@+id/search_engine_bar"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:paddingTop="1dp"
+ android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:choiceMode="singleChoice"
+ android:listSelector="@android:color/transparent"
+ android:cacheColorHint="@android:color/transparent" />
+
+</LinearLayout>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/layout/home_item_row.xml
@@ -0,0 +1,10 @@
+<?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"
+ style="@style/Widget.TwoLinePageRow"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/page_row_height"
+ android:minHeight="@dimen/page_row_height"/>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/layout/home_search_item_row.xml
@@ -0,0 +1,12 @@
+<?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.SearchEngineRow xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/search_row_height"
+ android:duplicateParentState="true"
+ android:paddingTop="5dp"
+ android:paddingBottom="5dp"/>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/layout/home_suggestion_prompt.xml
@@ -0,0 +1,56 @@
+<?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/. -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/about_page_header_grey">
+
+ <LinearLayout android:id="@+id/prompt"
+ android:focusable="true"
+ android:layout_width="match_parent"
+ android:minHeight="48dp"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:paddingLeft="15dp"
+ android:paddingRight="15dp"
+ android:textSize="12sp">
+
+ <TextView android:id="@+id/suggestions_prompt_title"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:fontFamily="sans-serif"
+ android:textColor="@color/text_and_tabs_tray_grey"
+ android:layout_marginRight="10dp"/>
+
+ <TextView android:id="@+id/suggestions_prompt_no"
+ android:layout_height="32dp"
+ android:minWidth="72dp"
+ android:layout_width="wrap_content"
+ android:layout_marginRight="10dp"
+ android:textColor="@color/text_and_tabs_tray_grey"
+ android:gravity="center"
+ android:background="@drawable/search_suggestion_prompt_no"
+ android:nextFocusRight="@+id/suggestions_prompt_yes"
+ android:focusable="true"
+ android:text="@string/button_no"/>
+
+ <TextView android:id="@+id/suggestions_prompt_yes"
+ android:layout_height="32dp"
+ android:minWidth="72dp"
+ android:layout_width="wrap_content"
+ android:gravity="center"
+ android:textColor="@android:color/white"
+ android:background="@drawable/search_suggestion_prompt_yes"
+ android:focusable="true"
+ android:text="@string/button_yes"/>
+
+
+
+ </LinearLayout>
+
+</FrameLayout>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/layout/search_engine_row.xml
@@ -0,0 +1,34 @@
+<?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/. -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <org.mozilla.gecko.widget.FaviconView android:id="@+id/suggestion_icon"
+ android:layout_width="@dimen/favicon_bg"
+ android:layout_height="@dimen/favicon_bg"
+ android:layout_marginLeft="16dip"
+ android:layout_marginStart="16dip"
+ android:layout_marginRight="16dip"
+ android:layout_marginEnd="16dip"
+ android:layout_centerVertical="true"
+ android:minWidth="@dimen/favicon_bg"
+ android:minHeight="@dimen/favicon_bg"/>
+
+ <org.mozilla.gecko.widget.FlowLayout android:id="@+id/suggestion_layout"
+ android:layout_toRightOf="@id/suggestion_icon"
+ android:layout_toEndOf="@id/suggestion_icon"
+ android:layout_centerVertical="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="15dp"
+ android:layout_marginEnd="15dp"
+ android:duplicateParentState="true">
+
+ <include layout="@layout/suggestion_item"
+ android:id="@+id/suggestion_user_entered"/>
+
+ </org.mozilla.gecko.widget.FlowLayout>
+
+</merge>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/layout/suggestion_item.xml
@@ -0,0 +1,31 @@
+<?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/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="32dp"
+ android:orientation="horizontal"
+ android:background="@drawable/search_suggestion_button"
+ android:gravity="center_vertical"
+ android:clickable="true"
+ android:padding="5dp">
+
+ <ImageView android:id="@+id/suggestion_item_icon"
+ android:src="@drawable/icon_most_recent_empty"
+ android:layout_marginRight="3dip"
+ android:layout_width="18dip"
+ android:layout_height="18dip"
+ android:visibility="gone"/>
+
+ <TextView android:id="@+id/suggestion_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/text_and_tabs_tray_grey"
+ android:textSize="14sp"
+ android:gravity="center_vertical"
+ android:layout_gravity="center_vertical"/>
+
+</LinearLayout>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/layout/two_line_page_row.xml
@@ -0,0 +1,54 @@
+<?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/. -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:gecko="http://schemas.android.com/apk/res-auto"
+ tools:context=".BrowserApp">
+
+ <org.mozilla.gecko.widget.FaviconView android:id="@+id/icon"
+ android:layout_width="@dimen/favicon_small_size"
+ android:layout_height="@dimen/favicon_small_size"
+ android:layout_margin="16dp"
+ tools:background="@drawable/favicon_globe"/>
+
+ <LinearLayout android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical"
+ android:paddingRight="10dp"
+ android:paddingEnd="10dp"
+ android:orientation="vertical">
+
+ <org.mozilla.gecko.widget.FadedSingleColorTextView
+ android:id="@+id/title"
+ style="@style/Widget.TwoLinePageRow.Title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ gecko:fadeWidth="90dp"
+ tools:text="This is a long test title"/>
+
+ <org.mozilla.gecko.widget.FadedSingleColorTextView android:id="@+id/url"
+ style="@style/Widget.TwoLinePageRow.Url"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:drawablePadding="5dp"
+ android:maxLength="1024"
+ gecko:fadeWidth="90dp"
+ tools:text="http://test.com/test"
+ tools:drawableLeft="@drawable/ic_url_bar_tab"
+ tools:drawableStart="@drawable/ic_url_bar_tab"
+ />
+
+ </LinearLayout>
+
+ <ImageView android:id="@+id/status_icon_bookmark"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:layout_gravity="center"
+ android:visibility="gone"
+ android:src="@drawable/star_blue"/>
+
+</merge>
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -705,17 +705,16 @@ gbjar.sources += ['java/org/mozilla/geck
'home/PanelViewAdapter.java',
'home/PanelViewItemHandler.java',
'home/PinSiteDialog.java',
'home/RecentTabsAdapter.java',
'home/RemoteTabsExpandableListState.java',
'home/SearchEngine.java',
'home/SearchEngineAdapter.java',
'home/SearchEngineBar.java',
- 'home/SearchEngineRow.java',
'home/SearchLoader.java',
'home/SimpleCursorLoader.java',
'home/SpacingDecoration.java',
'home/TabMenuStrip.java',
'home/TabMenuStripLayout.java',
'home/TopSitesGridItemView.java',
'home/TopSitesGridView.java',
'home/TopSitesPanel.java',
@@ -1007,21 +1006,23 @@ gbjar.sources += ['java/org/mozilla/geck
'widget/themed/ThemedLinearLayout.java',
'widget/themed/ThemedRelativeLayout.java',
'widget/themed/ThemedTextSwitcher.java',
'widget/themed/ThemedTextView.java',
'widget/themed/ThemedView.java',
]]
if CONFIG['MOZ_ANDROID_PHOTON']:
gbjar.sources += ['../app/src/photon/java/org/mozilla/gecko/' + x for x in [
+ 'home/SearchEngineRow.java',
'skin/SkinConfig.java',
'toolbar/TabCounter.java',
]]
else:
gbjar.sources += ['../app/src/australis/java/org/mozilla/gecko/' + x for x in [
+ 'home/SearchEngineRow.java',
'skin/SkinConfig.java',
'toolbar/TabCounter.java',
]]
android_package_dir = CONFIG['ANDROID_PACKAGE_NAME'].replace('.', '/')
gbjar.generated_sources = [] # Keep it this way.
gbjar.extra_jars += [
CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],