author | Ed Morley <emorley@mozilla.com> |
Wed, 21 Aug 2013 12:56:05 +0100 | |
changeset 143588 | d25f59701c033c1e63c4b5a6eae9919a238e78d4 |
parent 143587 | 072341c2c66f4ec9b65a56d826a4fd52f512bf62 (current diff) |
parent 143572 | b2486721572e18f2b936f98f68624301dc490ad8 (diff) |
child 143594 | e78c725d45bd197d87c8732f8877925f2967a5d4 |
push id | 25131 |
push user | emorley@mozilla.com |
push date | Wed, 21 Aug 2013 11:56:22 +0000 |
treeherder | mozilla-central@d25f59701c03 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 26.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/build/mobile/robocop/Actions.java.in +++ b/build/mobile/robocop/Actions.java.in @@ -78,12 +78,19 @@ public interface Actions { * @param key The special key to send */ void sendSpecialKey(SpecialKey key); void sendKeyCode(int keyCode); void drag(int startingX, int endingX, int startingY, int endingY); /** + * This is the implementation of clickLongOnScreen from Robotium 4.0 since this sometimes fails for Robotium 3.6 + * TODO : Remove this when Robotium is updated + */ + + void clickLongOnScreen(float x, float y); + + /** * Run a sql query on the specified database */ public Cursor querySql(String dbPath, String sql); }
--- a/build/mobile/robocop/FennecNativeActions.java.in +++ b/build/mobile/robocop/FennecNativeActions.java.in @@ -17,17 +17,19 @@ import java.util.ArrayList; import android.app.Activity; import android.app.Instrumentation; import android.content.Context; import android.database.Cursor; import android.os.SystemClock; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import com.jayway.android.robotium.solo.Solo; import static @ANDROID_PACKAGE_NAME@.FennecNativeDriver.LogLevel; public class FennecNativeActions implements Actions { private Solo mSolo; private Instrumentation mInstr; @@ -453,16 +455,51 @@ public class FennecNativeActions impleme public void sendKeys(String input) { mInstr.sendStringSync(input); } public void drag(int startingX, int endingX, int startingY, int endingY) { mSolo.drag(startingX, endingX, startingY, endingY, 10); } + /** + * This is the implementation of clickLongOnScreen from Robotium 4.0 since this sometimes fails for Robotium 3.6 + * TODO : Remove this when Robotium is updated + */ + + public void clickLongOnScreen(float x, float y) { + boolean successfull = false; + int retry = 0; + long downTime = SystemClock.uptimeMillis(); + long eventTime = SystemClock.uptimeMillis(); + MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0); + + while(!successfull && retry < 10) { + try{ + mInstr.sendPointerSync(event); + successfull = true; + }catch(SecurityException e){ + FennecNativeDriver.log(LogLevel.ERROR, e); + retry++; + } + } + + mAsserter.ok(successfull, "Trying to click on long on screen at (" + x + "," + y + ")", "Was able to click long on screen"); + + eventTime = SystemClock.uptimeMillis(); + event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x + 1.0f, y + 1.0f, 0); + mInstr.sendPointerSync(event); + mSolo.sleep(((int)(ViewConfiguration.getLongPressTimeout() * 2.5f))); + + eventTime = SystemClock.uptimeMillis(); + event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); + mInstr.sendPointerSync(event); + mSolo.sleep(500); + } + public Cursor querySql(String dbPath, String sql) { try { return (Cursor)mQuerySql.invoke(mRobocopApi, dbPath, sql); } catch(InvocationTargetException ex) { Log.e(LOGTAG, "Error invoking method", ex); } catch(IllegalAccessException ex) { Log.e(LOGTAG, "Error using field", ex); }
--- a/mobile/android/base/ActivityHandlerHelper.java +++ b/mobile/android/base/ActivityHandlerHelper.java @@ -31,17 +31,16 @@ import java.util.concurrent.TimeUnit; public class ActivityHandlerHelper implements GeckoEventListener { private static final String LOGTAG = "GeckoActivityHandlerHelper"; private final ConcurrentLinkedQueue<String> mFilePickerResult; private final ActivityResultHandlerMap mActivityResultHandlerMap; private final FilePickerResultHandlerSync mFilePickerResultHandlerSync; - private final AwesomebarResultHandler mAwesomebarResultHandler; private final CameraImageResultHandler mCameraImageResultHandler; private final CameraVideoResultHandler mCameraVideoResultHandler; public interface FileResultHandler { public void gotFile(String filename); } @SuppressWarnings("serial") @@ -53,17 +52,16 @@ public class ActivityHandlerHelper imple GeckoAppShell.sendEventToGecko(GeckoEvent.createNoOpEvent()); return true; } return false; } }; mActivityResultHandlerMap = new ActivityResultHandlerMap(); mFilePickerResultHandlerSync = new FilePickerResultHandlerSync(mFilePickerResult); - mAwesomebarResultHandler = new AwesomebarResultHandler(); mCameraImageResultHandler = new CameraImageResultHandler(mFilePickerResult); mCameraVideoResultHandler = new CameraVideoResultHandler(mFilePickerResult); GeckoAppShell.getEventDispatcher().registerEventListener("FilePicker:Show", this); } @Override public void handleMessage(String event, final JSONObject message) { if (event.equals("FilePicker:Show")) { @@ -86,20 +84,16 @@ public class ActivityHandlerHelper imple } GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent( "FilePicker:Result", message.toString())); } }); } } - public int makeRequestCodeForAwesomebar() { - return mActivityResultHandlerMap.put(mAwesomebarResultHandler); - } - public int makeRequestCode(ActivityResultHandler aHandler) { return mActivityResultHandlerMap.put(aHandler); } public void startIntentForActivity (Activity activity, Intent intent, ActivityResultHandler activityResultHandler) { activity.startActivityForResult(intent, mActivityResultHandlerMap.put(activityResultHandler)); }
--- a/mobile/android/base/AndroidManifest.xml.in +++ b/mobile/android/base/AndroidManifest.xml.in @@ -225,21 +225,16 @@ <activity android:name="org.mozilla.gecko.VideoPlayer" android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation" android:theme="@android:style/Theme.NoTitleBar"> <intent-filter> <action android:name="org.mozilla.gecko.PLAY_VIDEO" /> </intent-filter> </activity> - <activity android:name="org.mozilla.gecko.AwesomeBar" - android:theme="@style/Gecko.AwesomeBar" - android:configChanges="orientation|screenSize" - android:windowSoftInputMode="stateUnspecified|adjustResize"/> - <activity android:name="org.mozilla.gecko.GeckoPreferences" android:theme="@style/Gecko.Preferences" android:label="@string/settings_title" android:configChanges="orientation|screenSize" android:excludeFromRecents="true"/> <provider android:name="org.mozilla.gecko.db.BrowserProvider" android:authorities="@ANDROID_PACKAGE_NAME@.db.browser"
--- a/mobile/android/base/AnimatedHeightLayout.java +++ b/mobile/android/base/AnimatedHeightLayout.java @@ -12,18 +12,22 @@ import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; import android.widget.RelativeLayout; public class AnimatedHeightLayout extends RelativeLayout { private static final String LOGTAG = "GeckoAnimatedHeightLayout"; private static final int ANIMATION_DURATION = 100; private boolean mAnimating = false; + public AnimatedHeightLayout(Context context) { + super(context, null); + } + public AnimatedHeightLayout(Context context, AttributeSet attrs) { - super(context, attrs); + super(context, attrs, 0); } public AnimatedHeightLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
new file mode 100644 --- /dev/null +++ b/mobile/android/base/AutocompleteHandler.java @@ -0,0 +1,10 @@ +/* -*- 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; + +public interface AutocompleteHandler { + void onAutocomplete(String res); +}
deleted file mode 100644 --- a/mobile/android/base/AwesomeBar.java +++ /dev/null @@ -1,730 +0,0 @@ -/* -*- 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.BrowserContract.Combined; -import org.mozilla.gecko.db.BrowserDB; -import org.mozilla.gecko.gfx.BitmapUtils; -import org.mozilla.gecko.health.BrowserHealthRecorder; -import org.mozilla.gecko.util.GamepadUtils; -import org.mozilla.gecko.util.StringUtils; -import org.mozilla.gecko.util.ThreadUtils; -import org.mozilla.gecko.util.UiAsyncTask; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Bundle; -import android.text.Editable; -import android.text.InputType; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.AttributeSet; -import android.util.Log; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.MenuInflater; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.TabWidget; -import android.widget.Toast; - -import org.json.JSONObject; - -import java.net.URLEncoder; - -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"; - public static final String TAB_KEY = "tab"; - public static final String CURRENT_URL_KEY = "currenturl"; - public static final String TARGET_KEY = "target"; - public static final String SEARCH_KEY = "search"; - public static final String TITLE_KEY = "title"; - public static final String USER_ENTERED_KEY = "user_entered"; - public static final String READING_LIST_KEY = "reading_list"; - public static enum Target { NEW_TAB, CURRENT_TAB, PICK_SITE }; - - private String mTarget; - private AwesomeBarTabs mAwesomeTabs; - private CustomEditText mText; - private ImageButton mGoButton; - private ContextMenuSubject mContextMenuSubject; - 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) { - LayoutInflater.from(this).setFactory(this); - - super.onCreate(savedInstanceState); - - Log.d(LOGTAG, "creating awesomebar"); - - setContentView(R.layout.awesomebar); - - mGoButton = (ImageButton) findViewById(R.id.awesomebar_button); - mText = (CustomEditText) findViewById(R.id.awesomebar_text); - - TabWidget tabWidget = (TabWidget) findViewById(android.R.id.tabs); - tabWidget.setDividerDrawable(null); - - mAwesomeTabs = (AwesomeBarTabs) findViewById(R.id.awesomebar_tabs); - mAwesomeTabs.setOnUrlOpenListener(new AwesomeBarTabs.OnUrlOpenListener() { - @Override - public void onUrlOpen(String url, String title) { - openUrlAndFinish(url, title, false); - } - - @Override - public void onSearch(SearchEngine engine, String text) { - Intent resultIntent = new Intent(); - resultIntent.putExtra(URL_KEY, text); - resultIntent.putExtra(TARGET_KEY, mTarget); - resultIntent.putExtra(SEARCH_KEY, engine.name); - recordSearch(engine.identifier, "barsuggest"); - finishWithResult(resultIntent); - } - - @Override - public void onEditSuggestion(final String text) { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - mText.setText(text); - mText.setSelection(mText.getText().length()); - mText.requestFocus(); - } - }); - } - - @Override - public void onSwitchToTab(final int tabId) { - Intent resultIntent = new Intent(); - resultIntent.putExtra(TAB_KEY, Integer.toString(tabId)); - finishWithResult(resultIntent); - } - }); - - mGoButton.setOnClickListener(new Button.OnClickListener() { - @Override - public void onClick(View v) { - openUserEnteredAndFinish(mText.getText().toString()); - } - }); - - Intent intent = getIntent(); - String currentUrl = intent.getStringExtra(CURRENT_URL_KEY); - if (currentUrl != null) { - mText.setText(currentUrl); - mText.selectAll(); - } - - mTarget = intent.getStringExtra(TARGET_KEY); - if (mTarget.equals(Target.CURRENT_TAB.name())) { - Tab tab = Tabs.getInstance().getSelectedTab(); - if (tab != null && tab.isPrivate()) { - BrowserToolbarBackground mAddressBarBg = (BrowserToolbarBackground) findViewById(R.id.address_bar_bg); - mAddressBarBg.setPrivateMode(true); - - ShapedButton mTabs = (ShapedButton) findViewById(R.id.dummy_tab); - if (mTabs != null) - mTabs.setPrivateMode(true); - - mText.setPrivateMode(true); - } - } - mAwesomeTabs.setTarget(mTarget); - - mText.setOnKeyPreImeListener(new CustomEditText.OnKeyPreImeListener() { - @Override - public boolean onKeyPreIme(View v, int keyCode, KeyEvent event) { - // We only want to process one event per tap - if (event.getAction() != KeyEvent.ACTION_DOWN) - return false; - - if (keyCode == KeyEvent.KEYCODE_ENTER) { - // If the AwesomeBar has a composition string, don't submit the text yet. - // ENTER is needed to commit the composition string. - Editable content = mText.getText(); - if (!hasCompositionString(content)) { - openUserEnteredAndFinish(content.toString()); - return true; - } - } - - // If input method is in fullscreen mode, we want to dismiss - // it instead of closing awesomebar straight away. - InputMethodManager imm = - (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if (keyCode == KeyEvent.KEYCODE_BACK && !imm.isFullscreenMode()) { - return handleBackKey(); - } - - return false; - } - }); - - mText.addTextChangedListener(this); - - mText.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_ENTER) { - if (event.getAction() != KeyEvent.ACTION_DOWN) - return true; - - openUserEnteredAndFinish(mText.getText().toString()); - return true; - } else if (GamepadUtils.isBackKey(event)) { - return handleBackKey(); - } else { - return false; - } - } - }); - - mText.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (v == null || hasFocus) { - return; - } - - InputMethodManager imm = (InputMethodManager) - getSystemService(Context.INPUT_METHOD_SERVICE); - try { - imm.hideSoftInputFromWindow(v.getWindowToken(), 0); - } catch (NullPointerException e) { - Log.e(LOGTAG, "InputMethodManagerService, why are you throwing" - + " a NullPointerException? See bug 782096", e); - } - } - }); - - boolean showReadingList = intent.getBooleanExtra(READING_LIST_KEY, false); - if (showReadingList) { - BookmarksTab bookmarksTab = mAwesomeTabs.getBookmarksTab(); - bookmarksTab.setShowReadingList(true); - mAwesomeTabs.setCurrentItemByTag(bookmarksTab.getTag()); - } - } - - private boolean handleBackKey() { - // Let mAwesomeTabs try to handle the back press, since we may be in a - // bookmarks sub-folder. - if (mAwesomeTabs.onBackPressed()) - return true; - - // If mAwesomeTabs.onBackPressed() returned false, we didn't move up - // a folder level, so just exit the activity. - cancelAndFinish(); - return true; - } - - @Override - public void onConfigurationChanged(Configuration newConfiguration) { - super.onConfigurationChanged(newConfiguration); - } - - @Override - public boolean onSearchRequested() { - cancelAndFinish(); - return true; - } - - private void updateGoButton(String text) { - if (text.length() == 0) { - mGoButton.setVisibility(View.GONE); - return; - } - - mGoButton.setVisibility(View.VISIBLE); - - int imageResource = R.drawable.ic_awesomebar_go; - String contentDescription = getString(R.string.go); - int imeAction = EditorInfo.IME_ACTION_GO; - - int actionBits = mText.getImeOptions() & EditorInfo.IME_MASK_ACTION; - if (StringUtils.isSearchQuery(text, actionBits == EditorInfo.IME_ACTION_SEARCH)) { - imageResource = R.drawable.ic_awesomebar_search; - contentDescription = getString(R.string.search); - imeAction = EditorInfo.IME_ACTION_SEARCH; - } - - InputMethodManager imm = InputMethods.getInputMethodManager(mText.getContext()); - if (imm == null) { - return; - } - boolean restartInput = false; - if (actionBits != imeAction) { - int optionBits = mText.getImeOptions() & ~EditorInfo.IME_MASK_ACTION; - mText.setImeOptions(optionBits | imeAction); - - mDelayRestartInput = (imeAction == EditorInfo.IME_ACTION_GO) && - (InputMethods.shouldDelayAwesomebarUpdate(mText.getContext())); - if (!mDelayRestartInput) { - restartInput = true; - } - } else if (mDelayRestartInput) { - // Only call delayed restartInput when actionBits == imeAction - // so if there are two restarts in a row, the first restarts will - // be discarded and the second restart will be properly delayed - mDelayRestartInput = false; - restartInput = true; - } - if (restartInput) { - updateKeyboardInputType(); - imm.restartInput(mText); - mGoButton.setImageResource(imageResource); - mGoButton.setContentDescription(contentDescription); - } - } - - private void updateKeyboardInputType() { - // If the user enters a space, then we know they are entering search terms, not a URL. - // We can then switch to text mode so, - // 1) the IME auto-inserts spaces between words - // 2) the IME doesn't reset input keyboard to Latin keyboard. - String text = mText.getText().toString(); - int currentInputType = mText.getInputType(); - int newInputType = StringUtils.isSearchQuery(text, false) - ? (currentInputType & ~InputType.TYPE_TEXT_VARIATION_URI) // Text mode - : (currentInputType | InputType.TYPE_TEXT_VARIATION_URI); // URL mode - if (newInputType != currentInputType) { - mText.setRawInputType(newInputType); - } - } - - private void cancelAndFinish() { - setResult(Activity.RESULT_CANCELED); - finish(); - overridePendingTransition(R.anim.awesomebar_hold_still, R.anim.awesomebar_fade_out); - } - - private void finishWithResult(Intent intent) { - setResult(Activity.RESULT_OK, intent); - finish(); - overridePendingTransition(R.anim.awesomebar_hold_still, R.anim.awesomebar_fade_out); - } - - private void openUrlAndFinish(String url) { - openUrlAndFinish(url, null, false); - } - - private void openUrlAndFinish(String url, String title, boolean userEntered) { - Intent resultIntent = new Intent(); - resultIntent.putExtra(URL_KEY, url); - if (title != null && !TextUtils.isEmpty(title)) - resultIntent.putExtra(TITLE_KEY, title); - if (userEntered) - resultIntent.putExtra(USER_ENTERED_KEY, userEntered); - resultIntent.putExtra(TARGET_KEY, mTarget); - finishWithResult(resultIntent); - } - - /** - * Record in Health Report that a search has occurred. - * - * @param identifier - * a search identifier, such as "partnername". Can be null. - * @param where - * where the search was initialized; one of the values in - * {@link BrowserHealthRecorder#SEARCH_LOCATIONS}. - */ - private static void recordSearch(String identifier, String where) { - Log.i(LOGTAG, "Recording search: " + identifier + ", " + where); - try { - JSONObject message = new JSONObject(); - message.put("type", BrowserHealthRecorder.EVENT_SEARCH); - message.put("location", where); - message.put("identifier", identifier); - GeckoAppShell.getEventDispatcher().dispatchEvent(message); - } catch (Exception e) { - Log.w(LOGTAG, "Error recording search.", e); - } - } - - private void openUserEnteredAndFinish(final String url) { - final int index = url.indexOf(' '); - - // Check for a keyword if the URL looks like a search query - if (!StringUtils.isSearchQuery(url, true)) { - openUrlAndFinish(url, "", true); - return; - } - ThreadUtils.postToBackgroundThread(new Runnable() { - @Override - public void run() { - final String keyword; - final String keywordSearch; - - if (index == -1) { - keyword = url; - keywordSearch = ""; - } else { - keyword = url.substring(0, index); - keywordSearch = url.substring(index + 1); - } - - final String keywordUrl = BrowserDB.getUrlForKeyword(getContentResolver(), keyword); - final String searchUrl = (keywordUrl != null) - ? keywordUrl.replace("%s", URLEncoder.encode(keywordSearch)) - : url; - if (keywordUrl != null) { - recordSearch(null, "barkeyword"); - } - openUrlAndFinish(searchUrl, "", true); - } - }); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - // Galaxy Note sends key events for the stylus that are outside of the - // valid keyCode range (see bug 758427) - if (keyCode > KeyEvent.getMaxKeyCode()) - return true; - - // This method is called only if the key event was not handled - // by any of the views, which usually means the edit box lost focus - if (keyCode == KeyEvent.KEYCODE_BACK || - keyCode == KeyEvent.KEYCODE_MENU || - keyCode == KeyEvent.KEYCODE_DPAD_UP || - keyCode == KeyEvent.KEYCODE_DPAD_DOWN || - keyCode == KeyEvent.KEYCODE_DPAD_LEFT || - keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || - keyCode == KeyEvent.KEYCODE_DPAD_CENTER || - keyCode == KeyEvent.KEYCODE_DEL || - keyCode == KeyEvent.KEYCODE_VOLUME_UP || - keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || - GamepadUtils.isActionKey(event)) { - return super.onKeyDown(keyCode, event); - } else if (keyCode == KeyEvent.KEYCODE_SEARCH) { - mText.setText(""); - mText.requestFocus(); - return true; - } else { - int prevSelStart = mText.getSelectionStart(); - int prevSelEnd = mText.getSelectionEnd(); - - // Manually dispatch the key event to the AwesomeBar. If selection changed as - // a result of the key event, then give focus back to mText - mText.dispatchKeyEvent(event); - - int curSelStart = mText.getSelectionStart(); - int curSelEnd = mText.getSelectionEnd(); - if (prevSelStart != curSelStart || prevSelEnd != curSelEnd) { - mText.requestFocusFromTouch(); - // Restore the selection, which gets lost due to the focus switch - mText.setSelection(curSelStart, curSelEnd); - } - return true; - } - } - - @Override - public void onResume() { - super.onResume(); - if (mText != null && mText.getText() != null) { - updateGoButton(mText.getText().toString()); - if (mDelayRestartInput) { - // call updateGoButton again to force a restartInput call - updateGoButton(mText.getText().toString()); - } - } - - // Invlidate the cached value that keeps track of whether or - // not desktop bookmarks exist - BrowserDB.invalidateCachedState(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - mAwesomeTabs.destroy(); - } - - @Override - public void onBackPressed() { - // Let mAwesomeTabs try to handle the back press, since we may be in a - // bookmarks sub-folder. - if (mAwesomeTabs.onBackPressed()) - return; - - // Otherwise, just exit the awesome screen - cancelAndFinish(); - } - - static public class ContextMenuSubject { - public int id; - public String url; - public byte[] favicon; - public String title; - public String keyword; - public int display; - - public ContextMenuSubject(int id, String url, byte[] favicon, String title, String keyword) { - this(id, url, favicon, title, keyword, Combined.DISPLAY_NORMAL); - } - - public ContextMenuSubject(int id, String url, byte[] favicon, String title, String keyword, int display) { - this.id = id; - this.url = url; - this.favicon = favicon; - this.title = title; - this.keyword = keyword; - this.display = display; - } - }; - - @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, view, menuInfo); - AwesomeBarTab tab = mAwesomeTabs.getAwesomeBarTabForView(view); - mContextMenuSubject = tab.getSubject(menu, view, menuInfo); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - if (mContextMenuSubject == null) - return false; - - final int id = mContextMenuSubject.id; - final String url = mContextMenuSubject.url; - final byte[] b = mContextMenuSubject.favicon; - final String title = mContextMenuSubject.title; - final String keyword = mContextMenuSubject.keyword; - final int display = mContextMenuSubject.display; - - final int itemId = item.getItemId(); - if (itemId == R.id.open_private_tab || itemId == R.id.open_new_tab) { - if (url == null) { - Log.e(LOGTAG, "Can't open in new tab because URL is null"); - } - - String newTabUrl = url; - if (display == Combined.DISPLAY_READER) - newTabUrl = ReaderModeUtils.getAboutReaderForUrl(url, true); - - int flags = Tabs.LOADURL_NEW_TAB; - if (item.getItemId() == R.id.open_private_tab) - flags |= Tabs.LOADURL_PRIVATE; - - Tabs.getInstance().loadUrl(newTabUrl, flags); - Toast.makeText(this, R.string.new_tab_opened, Toast.LENGTH_SHORT).show(); - - return true; - } - - if (itemId == R.id.open_in_reader) { - if (url == null) { - Log.e(LOGTAG, "Can't open in reader mode because URL is null"); - } else { - openUrlAndFinish(ReaderModeUtils.getAboutReaderForUrl(url, true)); - } - return true; - } - - if (itemId == R.id.edit_bookmark) { - new EditBookmarkDialog(this).show(id, title, url, keyword); - return true; - } - - if (itemId == R.id.remove_bookmark) { - (new UiAsyncTask<Void, Void, Integer>(ThreadUtils.getBackgroundHandler()) { - private boolean mInReadingList; - - @Override - public void onPreExecute() { - mInReadingList = mAwesomeTabs.isInReadingList(); - } - - @Override - public Integer doInBackground(Void... params) { - BrowserDB.removeBookmark(getContentResolver(), id); - Integer count = mInReadingList ? - BrowserDB.getReadingListCount(getContentResolver()) : 0; - - return count; - } - - @Override - public void onPostExecute(Integer aCount) { - int messageId = R.string.bookmark_removed; - if (mInReadingList) { - messageId = R.string.reading_list_removed; - - GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Remove", url); - GeckoAppShell.sendEventToGecko(e); - - // Delete from Awesomebar context menu can alter reading list bookmark count - e = GeckoEvent.createBroadcastEvent("Reader:ListCountUpdated", Integer.toString(aCount)); - GeckoAppShell.sendEventToGecko(e); - } - - Toast.makeText(AwesomeBar.this, messageId, Toast.LENGTH_SHORT).show(); - } - }).execute(); - - return true; - } - - if (itemId == R.id.remove_history) { - (new UiAsyncTask<Void, Void, Void>(ThreadUtils.getBackgroundHandler()) { - @Override - public Void doInBackground(Void... params) { - BrowserDB.removeHistoryEntry(getContentResolver(), id); - return null; - } - - @Override - public void onPostExecute(Void result) { - Toast.makeText(AwesomeBar.this, R.string.history_removed, Toast.LENGTH_SHORT).show(); - } - }).execute(); - - return true; - } - - if (itemId == R.id.add_to_launcher) { - if (url == null) { - Log.e(LOGTAG, "Can't add to home screen because URL is null"); - } else { - Bitmap bitmap = null; - if (b != null) { - bitmap = BitmapUtils.decodeByteArray(b); - } - - String shortcutTitle = TextUtils.isEmpty(title) ? url.replaceAll("^([a-z]+://)?(www\\.)?", "") : title; - GeckoAppShell.createShortcut(shortcutTitle, url, bitmap, ""); - } - - return true; - } - - if (itemId == R.id.share) { - if (url == null) { - Log.e(LOGTAG, "Can't share because URL is null"); - } else { - GeckoAppShell.openUriExternal(url, "text/plain", "", "", - Intent.ACTION_SEND, title); - } - - return true; - } - - return super.onContextItemSelected(item); - } - - public static String getReaderForUrl(String url) { - // FIXME: still need to define the final way to open items from - // reading list. For now, we're using an about:reader page. - return "about:reader?url=" + Uri.encode(url) + "&readingList=1"; - } - - private static boolean hasCompositionString(Editable content) { - Object[] spans = content.getSpans(0, content.length(), Object.class); - if (spans != null) { - for (Object span : spans) { - if ((content.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) { - // Found composition string. - return true; - } - } - } - 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 (TextUtils.isEmpty(mAutoCompleteResult) || !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) || - InputMethods.isGestureKeyboard(mText.getContext())) { - updateGoButton(text); - } - } - - @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 - } -}
deleted file mode 100644 --- a/mobile/android/base/AwesomeBarTabs.java +++ /dev/null @@ -1,381 +0,0 @@ -/* -*- 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 android.content.Context; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.StateListDrawable; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TabHost; -import android.widget.TabWidget; - -public class AwesomeBarTabs extends TabHost - implements LightweightTheme.OnChangeListener { - private static final String LOGTAG = "GeckoAwesomeBarTabs"; - - private Context mContext; - private GeckoActivity mActivity; - - private boolean mInflated; - private LayoutInflater mInflater; - private OnUrlOpenListener mUrlOpenListener; - private View.OnTouchListener mListTouchListener; - private boolean mSearching = false; - private String mTarget; - private ViewPager mViewPager; - private AwesomePagerAdapter mPagerAdapter; - - private AwesomeBarTab mTabs[]; - - public interface OnUrlOpenListener { - public void onUrlOpen(String url, String title); - public void onSearch(SearchEngine engine, String text); - public void onEditSuggestion(String suggestion); - public void onSwitchToTab(final int tabId); - } - - private class AwesomePagerAdapter extends PagerAdapter { - public AwesomePagerAdapter() { - super(); - } - - @Override - public Object instantiateItem(ViewGroup group, int index) { - AwesomeBarTab tab = mTabs[index]; - group.addView(tab.getView()); - return tab; - } - - @Override - public void destroyItem(ViewGroup group, int index, Object obj) { - AwesomeBarTab tab = (AwesomeBarTab)obj; - group.removeView(tab.getView()); - } - - @Override - public int getCount() { - if (mSearching) - return 1; - return mTabs.length; - } - - @Override - public boolean isViewFromObject(View view, Object object) { - return getAwesomeBarTabForView(view) == object; - } - } - - private AwesomeBarTab getCurrentAwesomeBarTab() { - int index = mViewPager.getCurrentItem(); - return mTabs[index]; - } - - public AwesomeBarTab getAwesomeBarTabForView(View view) { - String tag = (String)view.getTag(); - return getAwesomeBarTabForTag(tag); - } - - public AwesomeBarTab getAwesomeBarTabForTag(String tag) { - for (AwesomeBarTab tab : mTabs) { - if (tag.equals(tab.getTag())) { - return tab; - } - } - return null; - } - - public boolean onBackPressed() { - AwesomeBarTab tab = getCurrentAwesomeBarTab(); - if (tab == null) - return false; - return tab.onBackPressed(); - } - - public AwesomeBarTabs(Context context, AttributeSet attrs) { - super(context, attrs); - - Log.d(LOGTAG, "Creating AwesomeBarTabs"); - - mContext = context; - mActivity = (GeckoActivity) context; - - mInflated = false; - mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - // HACK: Without this, the onFinishInflate is called twice - // This issue is due to a bug when Android inflates a layout with a - // parent. Fixed in Honeycomb - if (mInflated) - return; - - mInflated = true; - - // This should be called before adding any tabs - // to the TabHost. - setup(); - - mListTouchListener = new View.OnTouchListener() { - @Override - public boolean onTouch(View view, MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - // take focus away from awesome bar to hide the keyboard - requestFocus(); - } - return false; - } - }; - - mTabs = new AwesomeBarTab[] { - new AllPagesTab(mContext), - new BookmarksTab(mContext), - new HistoryTab(mContext) - }; - - final TabWidget tabWidget = (TabWidget) findViewById(android.R.id.tabs); - // hide the strip since we aren't using the TabHost... - tabWidget.setStripEnabled(false); - - mViewPager = (ViewPager) findViewById(R.id.tabviewpager); - mPagerAdapter = new AwesomePagerAdapter(); - mViewPager.setAdapter(mPagerAdapter); - - mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { - @Override - public void onPageScrollStateChanged(int state) { } - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } - @Override - public void onPageSelected(int position) { - tabWidget.setCurrentTab(position); - styleSelectedTab(); - // take focus away from awesome bar to hide the keyboard - requestFocus(); - } - }); - - for (int i = 0; i < mTabs.length; i++) { - mTabs[i].setListTouchListener(mListTouchListener); - addAwesomeTab(mTabs[i].getTag(), - mTabs[i].getTitleStringId(), - i); - } - - // Initialize "All Pages" list with no filter - filter("", null); - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - mActivity.getLightweightTheme().addListener(this); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mActivity.getLightweightTheme().removeListener(this); - } - - @Override - public void onLightweightThemeChanged() { - styleSelectedTab(); - } - - @Override - public void onLightweightThemeReset() { - styleSelectedTab(); - } - - public void setCurrentItemByTag(String tag) { - mViewPager.setCurrentItem(getTabIdByTag(tag)); - } - - public int getTabIdByTag(String tag) { - for (int i = 0; i < mTabs.length; i++) { - if (tag.equals(mTabs[i].getTag())) { - return i; - } - } - return -1; - } - - private void styleSelectedTab() { - int selIndex = mViewPager.getCurrentItem(); - TabWidget tabWidget = getTabWidget(); - boolean isPrivate = false; - - if (mTarget != null && mTarget.equals(AwesomeBar.Target.CURRENT_TAB.name())) { - Tab tab = Tabs.getInstance().getSelectedTab(); - if (tab != null) - isPrivate = tab.isPrivate(); - } - - for (int i = 0; i < tabWidget.getTabCount(); i++) { - GeckoTextView view = (GeckoTextView) tabWidget.getChildTabViewAt(i); - if (isPrivate) { - view.resetTheme(); - view.setPrivateMode((i == selIndex) ? false : true); - } else { - if (i == selIndex) - view.resetTheme(); - else if (mActivity.getLightweightTheme().isEnabled()) - view.setTheme(mActivity.getLightweightTheme().isLightTheme()); - else - view.resetTheme(); - } - - if (i < (selIndex - 1)) - view.getBackground().setLevel(3); - else if (i == (selIndex - 1)) - view.getBackground().setLevel(1); - else if (i == (selIndex + 1)) - view.getBackground().setLevel(2); - else if (i > (selIndex + 1)) - view.getBackground().setLevel(4); - } - - if (selIndex == 0) - findViewById(R.id.tab_widget_left).getBackground().setLevel(1); - else - findViewById(R.id.tab_widget_left).getBackground().setLevel(0); - - if (selIndex == (tabWidget.getTabCount() - 1)) - findViewById(R.id.tab_widget_right).getBackground().setLevel(2); - else - findViewById(R.id.tab_widget_right).getBackground().setLevel(0); - } - - - private View addAwesomeTab(String id, int titleId, final int contentId) { - GeckoTextView indicatorView = (GeckoTextView) mInflater.inflate(R.layout.awesomebar_tab_indicator, null); - indicatorView.setText(titleId); - - getTabWidget().addView(indicatorView); - - // this MUST be done after tw.addView to overwrite the listener added by tabWidget - // which delegates to TabHost (which we don't have) - indicatorView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mViewPager.setCurrentItem(contentId, true); - } - }); - - return indicatorView; - } - - public void setOnUrlOpenListener(OnUrlOpenListener listener) { - mUrlOpenListener = listener; - for (AwesomeBarTab tab : mTabs) { - tab.setUrlListener(listener); - } - } - - public void destroy() { - for (AwesomeBarTab tab : mTabs) { - tab.destroy(); - } - } - - public AllPagesTab getAllPagesTab() { - return (AllPagesTab)getAwesomeBarTabForTag("allPages"); - } - - public BookmarksTab getBookmarksTab() { - return (BookmarksTab)getAwesomeBarTabForTag("bookmarks"); - } - - public HistoryTab getHistoryTab() { - return (HistoryTab)getAwesomeBarTabForTag("history"); - } - - public void filter(String searchTerm, AutocompleteHandler handler) { - - // If searching, disable left / right tab swipes - mSearching = searchTerm.length() != 0; - - // reset the pager adapter to force repopulating the cache - mViewPager.setAdapter(mPagerAdapter); - - // Ensure the 'All Pages' tab is selected - AllPagesTab allPages = getAllPagesTab(); - getTabWidget().setCurrentTab(getTabIdByTag(allPages.getTag())); - styleSelectedTab(); - - // Perform the actual search - allPages.filter(searchTerm, handler); - - // If searching, hide the tabs bar - findViewById(R.id.tab_widget_container).setVisibility(mSearching ? View.GONE : View.VISIBLE); - } - - public boolean isInReadingList() { - return getBookmarksTab().isInReadingList(); - } - - public void setTarget(String target) { - mTarget = target; - styleSelectedTab(); - if (mTarget.equals(AwesomeBar.Target.CURRENT_TAB.name())) { - Tab tab = Tabs.getInstance().getSelectedTab(); - if (tab != null && tab.isPrivate()) - ((BackgroundLayout) findViewById(R.id.tab_widget_container)).setPrivateMode(true); - } - } - - public static class BackgroundLayout extends GeckoLinearLayout { - private GeckoActivity mActivity; - - public BackgroundLayout(Context context, AttributeSet attrs) { - super(context, attrs); - mActivity = (GeckoActivity) context; - } - - @Override - public void onLightweightThemeChanged() { - LightweightThemeDrawable drawable = mActivity.getLightweightTheme().getColorDrawable(this); - if (drawable == null) - return; - - drawable.setAlpha(255, 0); - - StateListDrawable stateList = new StateListDrawable(); - stateList.addState(new int[] { R.attr.state_private }, new ColorDrawable(mActivity.getResources().getColor(R.color.background_private))); - stateList.addState(new int[] {}, drawable); - - int[] padding = new int[] { getPaddingLeft(), - getPaddingTop(), - getPaddingRight(), - getPaddingBottom() - }; - setBackgroundDrawable(stateList); - setPadding(padding[0], padding[1], padding[2], padding[3]); - } - - @Override - public void onLightweightThemeReset() { - int[] padding = new int[] { getPaddingLeft(), - getPaddingTop(), - getPaddingRight(), - getPaddingBottom() - }; - setBackgroundResource(R.drawable.address_bar_bg); - setPadding(padding[0], padding[1], padding[2], padding[3]); - } - } -}
deleted file mode 100644 --- a/mobile/android/base/AwesomebarResultHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -/* 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.util.ActivityResultHandler; - -import android.content.Intent; -import android.util.Log; - -class AwesomebarResultHandler implements ActivityResultHandler { - private static final String LOGTAG = "GeckoAwesomebarResultHandler"; - - @Override - public void onActivityResult(int resultCode, Intent data) { - if (data != null) { - String tab = data.getStringExtra(AwesomeBar.TAB_KEY); - if (tab != null) { - Tabs.getInstance().selectTab(Integer.parseInt(tab)); - return; - } - - String url = data.getStringExtra(AwesomeBar.URL_KEY); - AwesomeBar.Target target = AwesomeBar.Target.valueOf(data.getStringExtra(AwesomeBar.TARGET_KEY)); - String searchEngine = data.getStringExtra(AwesomeBar.SEARCH_KEY); - if (url != null && url.length() > 0) { - int flags = Tabs.LOADURL_NONE; - if (target == AwesomeBar.Target.NEW_TAB) { - flags |= Tabs.LOADURL_NEW_TAB; - } - if (data.getBooleanExtra(AwesomeBar.USER_ENTERED_KEY, false)) { - flags |= Tabs.LOADURL_USER_ENTERED; - } - Tabs.getInstance().loadUrl(url, searchEngine, -1, flags); - } - } - } -}
--- a/mobile/android/base/BackButton.java +++ b/mobile/android/base/BackButton.java @@ -68,17 +68,17 @@ public class BackButton extends ShapedBu @Override public void draw(Canvas canvas) { mCanvasDelegate.draw(canvas, mPath, getWidth(), getHeight()); // Draw the border on top. canvas.drawPath(mBorderPath, isPrivateMode() ? mBorderPrivatePaint : mBorderPaint); } - // The drawable is constructed as per @drawable/address_bar_nav_button. + // The drawable is constructed as per @drawable/url_bar_nav_button. @Override public void onLightweightThemeChanged() { Drawable drawable = mActivity.getLightweightTheme().getDrawable(this); if (drawable == null) return; Resources resources = getContext().getResources(); StateListDrawable stateList = new StateListDrawable(); @@ -90,11 +90,11 @@ public class BackButton extends ShapedBu stateList.addState(new int[] { R.attr.state_private }, new ColorDrawable(resources.getColor(R.color.background_private))); stateList.addState(new int[] {}, drawable); setBackgroundDrawable(stateList); } @Override public void onLightweightThemeReset() { - setBackgroundResource(R.drawable.address_bar_nav_button); + setBackgroundResource(R.drawable.url_bar_nav_button); } }
--- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -9,24 +9,26 @@ import org.mozilla.gecko.animation.Prope import org.mozilla.gecko.db.BrowserContract.Combined; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.gfx.BitmapUtils; import org.mozilla.gecko.gfx.GeckoLayerClient; import org.mozilla.gecko.gfx.ImmutableViewportMetrics; import org.mozilla.gecko.gfx.LayerView; import org.mozilla.gecko.gfx.PanZoomController; import org.mozilla.gecko.health.BrowserHealthReporter; +import org.mozilla.gecko.home.BrowserSearch; +import org.mozilla.gecko.home.HomePager; +import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; import org.mozilla.gecko.menu.GeckoMenu; import org.mozilla.gecko.util.Clipboard; import org.mozilla.gecko.util.FloatUtils; import org.mozilla.gecko.util.GamepadUtils; import org.mozilla.gecko.util.HardwareUtils; import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.gecko.util.UiAsyncTask; -import org.mozilla.gecko.widget.AboutHome; import org.mozilla.gecko.widget.GeckoActionProvider; import org.mozilla.gecko.widget.ButtonToast; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; @@ -44,16 +46,18 @@ import android.graphics.drawable.BitmapD import android.graphics.drawable.Drawable; import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.nfc.NfcAdapter; import android.nfc.NfcEvent; import android.os.Build; import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; import android.text.TextUtils; import android.util.Log; import android.view.InputDevice; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -65,25 +69,28 @@ import android.view.animation.Interpolat import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.Toast; import java.io.File; import java.io.InputStream; import java.net.URL; import java.util.EnumSet; +import java.util.List; import java.util.Vector; abstract public class BrowserApp extends GeckoApp implements TabsPanel.TabsLayoutChangeListener, PropertyAnimator.PropertyAnimationListener, View.OnKeyListener, GeckoLayerClient.OnMetricsChangedListener, - AboutHome.UriLoadListener, - AboutHome.LoadCompleteListener { + BrowserSearch.OnSearchListener, + BrowserSearch.OnEditSuggestionListener, + HomePager.OnNewTabsListener, + 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; @@ -91,18 +98,23 @@ abstract public class BrowserApp extends private static final int READER_ADD_FAILED = 1; private static final int READER_ADD_DUPLICATE = 2; private static final String ADD_SHORTCUT_TOAST = "add_shortcut_toast"; private static final String STATE_ABOUT_HOME_TOP_PADDING = "abouthome_top_padding"; 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 AboutHome mAboutHome; + private HomePager mHomePager; + private View mHomePagerContainer; protected Telemetry.Timer mAboutHomeStartupTimer = null; // Set the default session restore value private int mSessionRestore = -1; private static final int GECKO_TOOLS_MENU = -1; private static final int ADDON_MENU_OFFSET = 1000; private class MenuItemInfo { @@ -147,20 +159,16 @@ abstract public class BrowserApp extends private int mToolbarHeight = 0; // Stored value of whether the last metrics change allowed for toolbar // scrolling. private boolean mDynamicToolbarCanScroll = false; private Integer mPrefObserverId; - // Tag for the AboutHome fragment. The fragment is automatically attached - // after restoring from a saved state, so we use this tag to identify it. - private static final String ABOUTHOME_TAG = "abouthome"; - private SharedPreferencesHelper mSharedPreferencesHelper; private OrderedBroadcastHelper mOrderedBroadcastHelper; private BrowserHealthReporter mBrowserHealthReporter; private SiteIdentityPopup mSiteIdentityPopup; @@ -177,24 +185,24 @@ abstract public class BrowserApp extends case LOCATION_CHANGE: if (Tabs.getInstance().isSelectedTab(tab)) { maybeCancelFaviconLoad(tab); } // fall through case SELECTED: if (Tabs.getInstance().isSelectedTab(tab)) { if (isAboutHome(tab)) { - showAboutHome(); + showHomePager(tab.getAboutHomePage()); if (isDynamicToolbarEnabled()) { // Show the toolbar. mLayerView.getLayerMarginsAnimator().showMargins(false); } } else { - hideAboutHome(); + hideHomePager(); } if (mSiteIdentityPopup != null) mSiteIdentityPopup.dismiss(); final TabsPanel.Panel panel = tab.isPrivate() ? TabsPanel.Panel.PRIVATE_TABS : TabsPanel.Panel.NORMAL_TABS; @@ -238,37 +246,31 @@ abstract public class BrowserApp extends loadFavicon(tab); } break; } super.onTabChanged(tab, msg, data); } @Override - void handleClearHistory() { - super.handleClearHistory(); - updateAboutHomeTopSites(); - } - - @Override public boolean onKey(View v, int keyCode, KeyEvent event) { // Global onKey handler. This is called if the focused UI doesn't // handle the key event, and before Gecko swallows the events. if (event.getAction() != KeyEvent.ACTION_DOWN) { return false; } // Gamepad support only exists in API-level >= 9 if (Build.VERSION.SDK_INT >= 9 && (event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { switch (keyCode) { case KeyEvent.KEYCODE_BUTTON_Y: // Toggle/focus the address bar on gamepad-y button. if (mBrowserToolbar.isVisible()) { - if (isDynamicToolbarEnabled() && !mAboutHome.getUserVisibleHint()) { + if (isDynamicToolbarEnabled() && !mHomePager.isVisible()) { if (mLayerView != null) { mLayerView.getLayerMarginsAnimator().hideMargins(false); mLayerView.requestFocus(); } } else { // Just focus the address bar when about:home is visible // or when the dynamic toolbar isn't enabled. mBrowserToolbar.requestFocusFromTouch(); @@ -325,17 +327,21 @@ abstract public class BrowserApp extends } } return false; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (onKey(null, keyCode, event)) { + if (!mBrowserToolbar.isEditing() && onKey(null, keyCode, event)) { + return true; + } + + if (mBrowserToolbar.onKey(keyCode, event)) { return true; } return super.onKeyDown(keyCode, event); } void handleReaderListCountRequest() { ThreadUtils.postToBackgroundThread(new Runnable() { @@ -379,29 +385,16 @@ abstract public class BrowserApp extends final int count = BrowserDB.getReadingListCount(getContentResolver()); GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:ListCountUpdated", Integer.toString(count))); } }); } @Override - void onStatePurged() { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - if (mAboutHome != null) - mAboutHome.setLastTabsVisibility(false); - } - }); - - super.onStatePurged(); - } - - @Override protected int getSessionRestoreState(Bundle savedInstanceState) { if (mSessionRestore > -1) { return mSessionRestore; } return super.getSessionRestoreState(savedInstanceState); } @@ -422,38 +415,59 @@ abstract public class BrowserApp extends ((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideTabsTouchListener()); ((GeckoApp.MainLayout) mMainLayout).setMotionEventInterceptor(new MotionEventInterceptor() { @Override public boolean onInterceptMotionEvent(View view, MotionEvent event) { // If we get a gamepad panning MotionEvent while the focus is not on the layerview, // put the focus on the layerview and carry on if (mLayerView != null && !mLayerView.hasFocus() && GamepadUtils.isPanningControl(event)) { - if (mAboutHome.getUserVisibleHint()) { - mAboutHome.requestFocus(); + if (mHomePager.isVisible()) { + mLayerView.requestFocus(); } else { - mLayerView.requestFocus(); + mHomePager.requestFocus(); } } return false; } }); - // Find the Fragment if it was already added from a restored instance state. - mAboutHome = (AboutHome) getSupportFragmentManager().findFragmentByTag(ABOUTHOME_TAG); + mHomePager = (HomePager) findViewById(R.id.home_pager); + mHomePagerContainer = findViewById(R.id.home_pager_container); + + mBrowserSearchContainer = findViewById(R.id.search_container); + mBrowserSearch = (BrowserSearch) getSupportFragmentManager().findFragmentByTag(BROWSER_SEARCH_TAG); + if (mBrowserSearch == null) { + mBrowserSearch = BrowserSearch.newInstance(); + mBrowserSearch.setUserVisibleHint(false); + } + + mBrowserToolbar.setOnActivateListener(new BrowserToolbar.OnActivateListener() { + public void onActivate() { + enterEditingMode(); + } + }); - if (mAboutHome == null) { - // AboutHome will be dynamically attached and detached as - // about:home is shown. Adding/removing the fragment is not synchronous, - // so we can't use Fragment#isVisible() to determine whether the - // about:home is shown. Instead, we use Fragment#getUserVisibleHint() - // with the hint we set ourselves. - mAboutHome = AboutHome.newInstance(); - mAboutHome.setUserVisibleHint(false); - } + mBrowserToolbar.setOnCommitListener(new BrowserToolbar.OnCommitListener() { + public void onCommit() { + commitEditingMode(); + } + }); + + mBrowserToolbar.setOnDismissListener(new BrowserToolbar.OnDismissListener() { + public void onDismiss() { + dismissEditingMode(); + } + }); + + mBrowserToolbar.setOnFilterListener(new BrowserToolbar.OnFilterListener() { + public void onFilter(String searchText, AutocompleteHandler handler) { + filterEditingMode(searchText, handler); + } + }); // Intercept key events for gamepad shortcuts mBrowserToolbar.setOnKeyListener(this); if (mTabsPanel != null) { mTabsPanel.setTabsLayoutChangeListener(this); updateSideBarState(); } @@ -463,16 +477,17 @@ abstract public class BrowserApp extends registerEventListener("CharEncoding:Data"); registerEventListener("CharEncoding:State"); registerEventListener("Feedback:LastUrl"); registerEventListener("Feedback:OpenPlayStore"); registerEventListener("Feedback:MaybeLater"); registerEventListener("Telemetry:Gather"); registerEventListener("Settings:Show"); registerEventListener("Updater:Launch"); + registerEventListener("Reader:GoToReadingList"); Distribution.init(this, getPackageResourcePath()); JavaAddonManager.getInstance().init(getApplicationContext()); mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext()); mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext()); mBrowserHealthReporter = new BrowserHealthReporter(); if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) { @@ -488,17 +503,17 @@ abstract public class BrowserApp extends return new NdefMessage(new NdefRecord[] { NdefRecord.createUri(tab.getURL()) }); } }, this); } } if (savedInstanceState != null) { mDynamicToolbarEnabled = savedInstanceState.getBoolean(STATE_DYNAMIC_TOOLBAR_ENABLED); - mAboutHome.setTopPadding(savedInstanceState.getInt(STATE_ABOUT_HOME_TOP_PADDING)); + mHomePagerContainer.setPadding(0, savedInstanceState.getInt(STATE_ABOUT_HOME_TOP_PADDING), 0, 0); } // Listen to the dynamic toolbar pref mPrefObserverId = PrefsHelper.getPref(PREF_CHROME_DYNAMICTOOLBAR, new PrefsHelper.PrefHandlerBase() { @Override public void prefValue(String pref, boolean value) { if (value == mDynamicToolbarEnabled) { return; @@ -522,30 +537,47 @@ abstract public class BrowserApp extends // We want to be notified of changes to be able to switch mode // without restarting. return true; } }); } @Override + public void onBackPressed() { + if (getSupportFragmentManager().getBackStackEntryCount() > 0) { + super.onBackPressed(); + return; + } + + if (dismissEditingMode()) { + return; + } + + if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) { + mSiteIdentityPopup.dismiss(); + return; + } + + super.onBackPressed(); + } + + @Override public void onResume() { super.onResume(); unregisterEventListener("Prompt:ShowTop"); } @Override public void onPause() { super.onPause(); // Register for Prompt:ShowTop so we can foreground this activity even if it's hidden. registerEventListener("Prompt:ShowTop"); } - - private void showBookmarkDialog() { final Tab tab = Tabs.getInstance().getSelectedTab(); final Prompt ps = new Prompt(this, new Prompt.PromptCallback() { @Override public void onPromptFinished(String result) { int itemId = -1; try { itemId = new JSONObject(result).getInt("button"); @@ -578,24 +610,24 @@ abstract public class BrowserApp extends } private void setDynamicToolbarEnabled(boolean enabled) { if (enabled) { if (mLayerView != null) { mLayerView.getLayerClient().setOnMetricsChangedListener(this); } setToolbarMargin(0); - mAboutHome.setTopPadding(mBrowserToolbar.getHeight()); + mHomePagerContainer.setPadding(0, mBrowserToolbar.getHeight(), 0, 0); } else { // Immediately show the toolbar when disabling the dynamic // toolbar. if (mLayerView != null) { mLayerView.getLayerClient().setOnMetricsChangedListener(null); } - mAboutHome.setTopPadding(0); + mHomePagerContainer.setPadding(0, 0, 0, 0); if (mBrowserToolbar != null) { mBrowserToolbar.scrollTo(0, 0); } } refreshToolbarHeight(); } @@ -604,17 +636,18 @@ abstract public class BrowserApp extends } private boolean isAboutHome(Tab tab) { return TextUtils.equals(ABOUT_HOME, tab.getURL()); } @Override public boolean onSearchRequested() { - return showAwesomebar(AwesomeBar.Target.CURRENT_TAB); + enterEditingMode(); + return true; } @Override public boolean onContextItemSelected(MenuItem item) { final int itemId = item.getItemId(); if (itemId == R.id.pasteandgo) { String text = Clipboard.getText(); if (!TextUtils.isEmpty(text)) { @@ -626,17 +659,17 @@ abstract public class BrowserApp extends if (itemId == R.id.site_settings) { GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Permissions:Get", null)); return true; } if (itemId == R.id.paste) { String text = Clipboard.getText(); if (!TextUtils.isEmpty(text)) { - showAwesomebar(AwesomeBar.Target.CURRENT_TAB, text); + enterEditingMode(text); } return true; } if (itemId == R.id.share) { shareCurrentUrl(); return true; } @@ -685,51 +718,16 @@ abstract public class BrowserApp extends }); } return true; } return false; } - public boolean showAwesomebar(AwesomeBar.Target aTarget) { - return showAwesomebar(aTarget, null); - } - - public boolean showAwesomebar(AwesomeBar.Target aTarget, String aUrl) { - Intent intent = new Intent(getBaseContext(), AwesomeBar.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); - intent.putExtra(AwesomeBar.TARGET_KEY, aTarget.name()); - - // If we were passed in a URL, show it. - if (aUrl != null && !TextUtils.isEmpty(aUrl)) { - intent.putExtra(AwesomeBar.CURRENT_URL_KEY, aUrl); - } else if (aTarget == AwesomeBar.Target.CURRENT_TAB) { - // Otherwise, if we're editing the current tab, show its URL. - Tab tab = Tabs.getInstance().getSelectedTab(); - if (tab != null) { - // Check to see if there's a user-entered search term, which we save - // whenever the user performs a search. - aUrl = tab.getUserSearch(); - if (TextUtils.isEmpty(aUrl)) { - aUrl = tab.getURL(); - } - if (aUrl != null) { - intent.putExtra(AwesomeBar.CURRENT_URL_KEY, aUrl); - } - } - } - - int requestCode = GeckoAppShell.sActivityHelper.makeRequestCodeForAwesomebar(); - startActivityForResult(intent, requestCode); - overridePendingTransition (R.anim.awesomebar_fade_in, R.anim.awesomebar_hold_still); - return true; - } - - @Override public void setAccessibilityEnabled(boolean enabled) { if (mAccessibilityEnabled == enabled) { return; } // Disable the dynamic toolbar when accessibility features are enabled, // and re-read the preference when they're disabled. @@ -771,16 +769,17 @@ abstract public class BrowserApp extends unregisterEventListener("CharEncoding:Data"); unregisterEventListener("CharEncoding:State"); unregisterEventListener("Feedback:LastUrl"); unregisterEventListener("Feedback:OpenPlayStore"); unregisterEventListener("Feedback:MaybeLater"); unregisterEventListener("Telemetry:Gather"); unregisterEventListener("Settings:Show"); unregisterEventListener("Updater:Launch"); + unregisterEventListener("Reader:GoToReadingList"); if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) { NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this); if (nfc != null) { // null this out even though the docs say it's not needed, // because the source code looks like it will only do this // automatically on API 14+ nfc.setNdefPushMessageCallback(null, this); @@ -838,17 +837,17 @@ abstract public class BrowserApp extends private void setToolbarMargin(int margin) { ((RelativeLayout.LayoutParams) mGeckoLayout.getLayoutParams()).topMargin = margin; mGeckoLayout.requestLayout(); } @Override public void onMetricsChanged(ImmutableViewportMetrics aMetrics) { - if (mAboutHome.getUserVisibleHint() || mBrowserToolbar == null) { + if (mHomePager.isVisible() || mBrowserToolbar == null) { return; } // If the page has shrunk so that the toolbar no longer scrolls, make // sure the toolbar is visible. if (aMetrics.getPageHeight() <= aMetrics.getHeight()) { if (mDynamicToolbarCanScroll) { mDynamicToolbarCanScroll = false; @@ -873,17 +872,17 @@ abstract public class BrowserApp extends }); if (mFormAssistPopup != null) mFormAssistPopup.onMetricsChanged(aMetrics); } @Override public void onPanZoomStopped() { - if (!isDynamicToolbarEnabled() || mAboutHome.getUserVisibleHint()) { + if (!isDynamicToolbarEnabled() || mHomePager.isVisible()) { return; } // Make sure the toolbar is fully hidden or fully shown when the user // lifts their finger. If the page is shorter than the viewport, the // toolbar is always shown. ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics(); if (metrics.getPageHeight() < metrics.getHeight() @@ -895,22 +894,29 @@ abstract public class BrowserApp extends } public void refreshToolbarHeight() { int height = 0; if (mBrowserToolbar != null) { height = mBrowserToolbar.getHeight(); } - if (!isDynamicToolbarEnabled()) { + if (!isDynamicToolbarEnabled() || mHomePager.isVisible()) { // Use aVisibleHeight here so that when the dynamic toolbar is // enabled, the padding will animate with the toolbar becoming // visible. - setToolbarMargin(height); - height = 0; + if (isDynamicToolbarEnabled()) { + // When the dynamic toolbar is enabled, set the padding on the + // about:home widget directly - this is to avoid resizing the + // LayerView, which can cause visible artifacts. + mHomePagerContainer.setPadding(0, height, 0, 0); + } else { + setToolbarMargin(height); + height = 0; + } } else { setToolbarMargin(0); } if (mLayerView != null && height != mToolbarHeight) { mToolbarHeight = height; mLayerView.getLayerMarginsAnimator().setMaxMargins(0, height, 0, 0); mLayerView.getLayerMarginsAnimator().showMargins(true); @@ -952,47 +958,21 @@ abstract public class BrowserApp extends invalidateOptionsMenu(); updateSideBarState(); mTabsPanel.refresh(); if (mSiteIdentityPopup != null) { mSiteIdentityPopup.dismiss(); } } - @Override - public void onBackPressed() { - if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) { - mSiteIdentityPopup.dismiss(); - return; - } - - super.onBackPressed(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - String url = null; - - // Don't update the url in the toolbar if the activity was cancelled. - if (resultCode == Activity.RESULT_OK && data != null) { - // Don't update the url if the activity was launched to pick a site. - String targetKey = data.getStringExtra(AwesomeBar.TARGET_KEY); - if (!AwesomeBar.Target.PICK_SITE.toString().equals(targetKey)) { - // Update the toolbar with the url that was just entered. - url = data.getStringExtra(AwesomeBar.URL_KEY); - } - } - - // We always need to call fromAwesomeBarSearch to perform the toolbar animation. - mBrowserToolbar.fromAwesomeBarSearch(url); - - // Trigger any tab-related events after we start restoring - // the toolbar state above to make ensure animations happen - // on the correct order. - super.onActivityResult(requestCode, resultCode, data); + public View getActionBarLayout() { + RelativeLayout actionBar = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.browser_toolbar, null); + actionBar.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, + (int) getResources().getDimension(R.dimen.browser_toolbar_height))); + return actionBar; } @Override public boolean hasTabsSideBar() { return (mTabsPanel != null && mTabsPanel.isSideBar()); } private void updateSideBarState() { @@ -1150,33 +1130,35 @@ abstract public class BrowserApp extends if (!message.isNull(GeckoPreferences.INTENT_EXTRA_RESOURCES)) { resource = message.getString(GeckoPreferences.INTENT_EXTRA_RESOURCES); } Intent settingsIntent = new Intent(this, GeckoPreferences.class); GeckoPreferences.setResourceToOpen(settingsIntent, resource); startActivity(settingsIntent); } else if (event.equals("Updater:Launch")) { handleUpdaterLaunch(); + } else if (event.equals("Reader:GoToReadingList")) { + openReadingList(); } else if (event.equals("Prompt:ShowTop")) { // Bring this activity to front so the prompt is visible.. Intent bringToFrontIntent = new Intent(); bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS); bringToFrontIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(bringToFrontIntent); } else { super.handleMessage(event, message); } } catch (Exception e) { Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e); } } @Override public void addTab() { - showAwesomebar(AwesomeBar.Target.NEW_TAB); + Tabs.getInstance().loadUrl("about:home", Tabs.LOADURL_NEW_TAB); } @Override public void addPrivateTab() { Tabs.getInstance().loadUrl("about:privatebrowsing", Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_PRIVATE); } @Override @@ -1229,17 +1211,17 @@ abstract public class BrowserApp extends mMainLayoutAnimator.stop(false); } if (areTabsShown()) { mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); } mMainLayoutAnimator = new PropertyAnimator(animationLength, sTabsInterpolator); - mMainLayoutAnimator.setPropertyAnimationListener(this); + mMainLayoutAnimator.addPropertyAnimationListener(this); if (hasTabsSideBar()) { mMainLayoutAnimator.attach(mMainLayout, PropertyAnimator.Property.SCROLL_X, -width); } else { mMainLayoutAnimator.attach(mMainLayout, PropertyAnimator.Property.SCROLL_Y, @@ -1279,17 +1261,76 @@ abstract public class BrowserApp extends mMainLayoutAnimator = null; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mToast.onSaveInstanceState(outState); outState.putBoolean(STATE_DYNAMIC_TOOLBAR_ENABLED, mDynamicToolbarEnabled); - outState.putInt(STATE_ABOUT_HOME_TOP_PADDING, mAboutHome.getTopPadding()); + outState.putInt(STATE_ABOUT_HOME_TOP_PADDING, mHomePagerContainer.getPaddingTop()); + } + + /** + * Attempts to switch to an open tab with the given URL. + * + * @return true if we successfully switched to a tab, false otherwise. + */ + private boolean maybeSwitchToTab(String url, EnumSet<OnUrlOpenListener.Flags> flags) { + if (!flags.contains(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB)) { + return false; + } + + final Tabs tabs = Tabs.getInstance(); + final int tabId = tabs.getTabIdForUrl(url); + if (tabId < 0) { + return false; + } + + // If this tab is already selected, just hide the home pager. + if (tabs.isSelectedTab(tabs.getTab(tabId))) { + hideHomePager(); + } else { + tabs.selectTab(tabId); + } + + hideBrowserSearch(); + mBrowserToolbar.cancelEdit(); + + return true; + } + + private void openUrl(String url) { + openUrl(url, null, false); + } + + private void openUrl(String url, boolean newTab) { + openUrl(url, null, newTab); + } + + private void openUrl(String url, String searchEngine) { + openUrl(url, searchEngine, false); + } + + private void openUrl(String url, String searchEngine, boolean newTab) { + mBrowserToolbar.setProgressVisibility(true); + + int flags = Tabs.LOADURL_NONE; + if (newTab) { + flags |= Tabs.LOADURL_NEW_TAB; + } + + Tabs.getInstance().loadUrl(url, searchEngine, -1, flags); + + hideBrowserSearch(); + mBrowserToolbar.cancelEdit(); + } + + private void openReadingList() { + Tabs.getInstance().loadUrl(ABOUT_HOME, Tabs.LOADURL_READING_LIST); } /* Favicon methods */ private void loadFavicon(final Tab tab) { maybeCancelFaviconLoad(tab); int flags = Favicons.FLAG_SCALE | ( (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : Favicons.FLAG_PERSIST); long id = Favicons.getInstance().loadFavicon(tab.getURL(), tab.getFaviconURL(), flags, @@ -1325,67 +1366,157 @@ abstract public class BrowserApp extends // Cancel pending favicon load task Favicons.getInstance().cancelFaviconLoad(faviconLoadId); // Reset favicon load state tab.setFaviconLoadId(Favicons.NOT_LOADING); } + private void enterEditingMode() { + String url = null; - /* About:home UI */ - void updateAboutHomeTopSites() { - mAboutHome.update(EnumSet.of(AboutHome.UpdateFlags.TOP_SITES)); + final Tab tab = Tabs.getInstance().getSelectedTab(); + if (tab != null) { + final String userSearch = tab.getUserSearch(); + + // Check to see if there's a user-entered search term, + // which we save whenever the user performs a search. + url = (TextUtils.isEmpty(userSearch) ? tab.getURL() : userSearch); + } + + enterEditingMode(url); + } + + /** + * Enters editing mode for the current tab. This method will + * always open the VISITED page on about:home. + */ + private void enterEditingMode(String url) { + if (url == null) { + throw new IllegalArgumentException("Cannot handle null URLs in enterEditingMode"); + } + + final PropertyAnimator animator = new PropertyAnimator(250); + animator.setUseHardwareLayer(false); + + mBrowserToolbar.startEditing(url, animator); + showHomePagerWithAnimator(HomePager.Page.HISTORY, animator); + + animator.start(); } - private void showAboutHome() { - if (mAboutHome.getUserVisibleHint()) { + void commitEditingMode() { + if (!mBrowserToolbar.isEditing()) { + return; + } + + final String url = mBrowserToolbar.commitEdit(); + animateHideHomePager(); + hideBrowserSearch(); + + if (!TextUtils.isEmpty(url)) { + Tabs.getInstance().loadUrl(url, Tabs.LOADURL_USER_ENTERED); + } + } + + boolean dismissEditingMode() { + if (!mBrowserToolbar.isEditing()) { + return false; + } + + mBrowserToolbar.cancelEdit(); + animateHideHomePager(); + hideBrowserSearch(); + + return true; + } + + void filterEditingMode(String searchTerm, AutocompleteHandler handler) { + if (TextUtils.isEmpty(searchTerm)) { + hideBrowserSearch(); + } else { + showBrowserSearch(); + mBrowserSearch.filter(searchTerm, handler); + } + } + + private void showHomePager(HomePager.Page page) { + showHomePagerWithAnimator(page, null); + } + + private void showHomePagerWithAnimator(HomePager.Page page, PropertyAnimator animator) { + if (mHomePager.isVisible()) { return; } // Refresh toolbar height to possibly restore the toolbar padding refreshToolbarHeight(); // Show the toolbar before hiding about:home so the // onMetricsChanged callback still works. if (isDynamicToolbarEnabled() && mLayerView != null) { mLayerView.getLayerMarginsAnimator().showMargins(true); } - // We use commitAllowingStateLoss() instead of commit() here to avoid an - // IllegalStateException. showAboutHome() and hideAboutHome() are - // executed inside of tab's onChange() callback. Since that callback can - // be triggered asynchronously from Gecko, it's possible that this - // method can be called while Fennec is in the background. If that - // happens, using commit() would throw an IllegalStateException since - // it can't be used between the Activity's onSaveInstanceState() and - // onResume(). - getSupportFragmentManager().beginTransaction() - .add(R.id.gecko_layout, mAboutHome, ABOUTHOME_TAG).commitAllowingStateLoss(); - mAboutHome.setUserVisibleHint(true); + mHomePager.show(getSupportFragmentManager(), page, animator); + } - mBrowserToolbar.setNextFocusDownId(R.id.abouthome_content); + private void animateHideHomePager() { + hideHomePagerWithAnimation(true); } - private void hideAboutHome() { - if (!mAboutHome.getUserVisibleHint()) { + private void hideHomePager() { + hideHomePagerWithAnimation(false); + } + + private void hideHomePagerWithAnimation(boolean animate) { + if (!mHomePager.isVisible()) { return; } - getSupportFragmentManager().beginTransaction() - .remove(mAboutHome).commitAllowingStateLoss(); - mAboutHome.setUserVisibleHint(false); + final Tab tab = Tabs.getInstance().getSelectedTab(); + if (tab != null && isAboutHome(tab)) { + return; + } + + // FIXME: do animation if animate is true + mHomePager.hide(); 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. @@ -1989,25 +2120,42 @@ 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.OnNewTabsListener @Override - public void onAboutHomeUriLoad(String url) { - mBrowserToolbar.setProgressVisibility(true); - Tabs.getInstance().loadUrl(url); + public void onNewTabs(String[] urls) { + for (String url : urls) { + openUrl(url, true); + } } + // HomePager.OnUrlOpenListener @Override - public void onAboutHomeLoadComplete() { - mAboutHomeStartupTimer.stop(); + public void onUrlOpen(String url, EnumSet<OnUrlOpenListener.Flags> flags) { + if (!maybeSwitchToTab(url, flags)) { + openUrl(url); + } + } + + // BrowserSearch.OnSearchListener + @Override + public void onSearch(String engineId, String text) { + openUrl(text, engineId); + } + + // BrowserSearch.OnEditSuggestionListener + @Override + public void onEditSuggestion(String suggestion) { + mBrowserToolbar.onEditSuggestion(suggestion); } @Override public int getLayout() { return R.layout.gecko_app; } @Override protected String getDefaultProfileName() { String profile = GeckoProfile.findDefaultProfile(this);
--- a/mobile/android/base/BrowserToolbar.java +++ b/mobile/android/base/BrowserToolbar.java @@ -4,16 +4,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko; import org.mozilla.gecko.animation.PropertyAnimator; import org.mozilla.gecko.animation.ViewHelper; import org.mozilla.gecko.gfx.ImmutableViewportMetrics; import org.mozilla.gecko.gfx.LayerView; +import org.mozilla.gecko.util.GamepadUtils; import org.mozilla.gecko.menu.GeckoMenu; import org.mozilla.gecko.menu.MenuPopup; import org.mozilla.gecko.PageActionLayout; import org.mozilla.gecko.PrefsHelper; import org.mozilla.gecko.util.Clipboard; import org.mozilla.gecko.util.StringUtils; import org.mozilla.gecko.util.HardwareUtils; import org.mozilla.gecko.util.ThreadUtils; @@ -29,95 +30,135 @@ import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.os.Build; import android.os.SystemClock; import android.text.style.ForegroundColorSpan; +import android.text.Editable; +import android.text.InputType; import android.text.Spannable; import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.TextUtils; +import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; import android.view.ContextMenu; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MenuInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; import android.view.Window; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.AlphaAnimation; import android.view.animation.Interpolator; import android.view.animation.TranslateAnimation; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.Button; +import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.PopupWindow; import android.widget.RelativeLayout; import android.widget.RelativeLayout.LayoutParams; import android.widget.ViewSwitcher; import java.util.Arrays; import java.util.List; public class BrowserToolbar extends GeckoRelativeLayout - implements Tabs.OnTabsChangedListener, + implements TextWatcher, + AutocompleteHandler, + Tabs.OnTabsChangedListener, GeckoMenu.ActionItemBarPresenter, Animation.AnimationListener, GeckoEventListener { private static final String LOGTAG = "GeckoToolbar"; public static final String PREF_TITLEBAR_MODE = "browser.chrome.titlebarMode"; + + public interface OnActivateListener { + public void onActivate(); + } + + public interface OnCommitListener { + public void onCommit(); + } + + public interface OnDismissListener { + public void onDismiss(); + } + + public interface OnFilterListener { + public void onFilter(String searchText, AutocompleteHandler handler); + } + private LayoutParams mAwesomeBarParams; private View mUrlDisplayContainer; - private View mAwesomeBarEntry; - private ImageView mAwesomeBarRightEdge; - private BrowserToolbarBackground mAddressBarBg; + private View mUrlEditContainer; + private CustomEditText mUrlEditText; + private View mUrlBarEntry; + private ImageView mUrlBarRightEdge; + private BrowserToolbarBackground mUrlBarBackground; private GeckoTextView mTitle; private int mTitlePadding; private boolean mSiteSecurityVisible; private boolean mSwitchingTabs; private ShapedButton mTabs; private ImageButton mBack; private ImageButton mForward; public ImageButton mFavicon; public ImageButton mStop; public ImageButton mSiteSecurity; + public ImageButton mGo; public PageActionLayout mPageActionLayout; private Animation mProgressSpinner; private TabCounter mTabsCounter; private ImageView mShadow; private GeckoImageButton mMenu; private GeckoImageView mMenuIcon; private LinearLayout mActionItemBar; private MenuPopup mMenuPopup; private List<? extends View> mFocusOrder; + private OnActivateListener mActivateListener; + private OnCommitListener mCommitListener; + private OnDismissListener mDismissListener; + private OnFilterListener mFilterListener; final private BrowserApp mActivity; private boolean mHasSoftMenuButton; private boolean mShowSiteSecurity; private boolean mShowReader; private boolean mSpinnerVisible; + 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; + + private boolean mIsEditing; private boolean mAnimatingEntry; private AlphaAnimation mLockFadeIn; private TranslateAnimation mTitleSlideLeft; private TranslateAnimation mTitleSlideRight; - private int mAddressBarViewOffset; + private int mUrlBarViewOffset; private int mDefaultForwardMargin; private PropertyAnimator mForwardAnim = null; private int mFaviconSize; private PropertyAnimator mVisibilityAnimator; private static final Interpolator sButtonsInterpolator = new AccelerateInterpolator(); @@ -145,16 +186,17 @@ public class BrowserToolbar extends Geck mActivity = (BrowserApp) context; // Inflate the content. LayoutInflater.from(context).inflate(R.layout.browser_toolbar, this); Tabs.registerOnTabsChangedListener(this); mSwitchingTabs = true; + mIsEditing = false; mAnimatingEntry = false; mShowUrl = false; // listen to the title bar pref. mPrefObserverId = PrefsHelper.getPref(PREF_TITLEBAR_MODE, new PrefsHelper.PrefHandlerBase() { @Override public void prefValue(String pref, String str) { int value = Integer.parseInt(str); @@ -190,29 +232,32 @@ public class BrowserToolbar extends Geck registerEventListener("Reader:Click"); registerEventListener("Reader:LongClick"); mShowSiteSecurity = false; mShowReader = false; mAnimatingEntry = false; - mAddressBarBg = (BrowserToolbarBackground) findViewById(R.id.address_bar_bg); - mAddressBarViewOffset = res.getDimensionPixelSize(R.dimen.addressbar_offset_left); + mUrlBarBackground = (BrowserToolbarBackground) findViewById(R.id.url_bar_bg); + mUrlBarViewOffset = res.getDimensionPixelSize(R.dimen.url_bar_offset_left); mDefaultForwardMargin = res.getDimensionPixelSize(R.dimen.forward_default_offset); - mUrlDisplayContainer = findViewById(R.id.awesome_bar_display_container); - mAwesomeBarEntry = findViewById(R.id.awesome_bar_entry); + mUrlDisplayContainer = findViewById(R.id.url_display_container); + mUrlBarEntry = findViewById(R.id.url_bar_entry); + + mUrlEditContainer = findViewById(R.id.url_edit_container); + mUrlEditText = (CustomEditText) findViewById(R.id.url_edit_text); // This will clip the right edge's image at half of its width - mAwesomeBarRightEdge = (ImageView) findViewById(R.id.awesome_bar_right_edge); - if (mAwesomeBarRightEdge != null) { - mAwesomeBarRightEdge.getDrawable().setLevel(5000); + mUrlBarRightEdge = (ImageView) findViewById(R.id.url_bar_right_edge); + if (mUrlBarRightEdge != null) { + mUrlBarRightEdge.getDrawable().setLevel(5000); } - mTitle = (GeckoTextView) findViewById(R.id.awesome_bar_title); + mTitle = (GeckoTextView) findViewById(R.id.url_bar_title); mTitlePadding = mTitle.getPaddingRight(); mTabs = (ShapedButton) findViewById(R.id.tabs); mTabsCounter = (TabCounter) findViewById(R.id.tabs_counter); mBack = (ImageButton) findViewById(R.id.back); mForward = (ImageButton) findViewById(R.id.forward); mForward.setEnabled(false); // initialize the forward button to not be enabled @@ -253,24 +298,30 @@ public class BrowserToolbar extends Geck @Override public void onAttachedToWindow() { super.onAttachedToWindow(); setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { - mActivity.autoHideTabs(); - onAwesomeBarSearch(); + if (mActivateListener != null) { + mActivateListener.onActivate(); + } } }); setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + // We don't the context menu while editing + if (isEditing()) { + return; + } + MenuInflater inflater = mActivity.getMenuInflater(); inflater.inflate(R.menu.titlebar_contextmenu, menu); String clipboard = Clipboard.getText(); if (TextUtils.isEmpty(clipboard)) { menu.findItem(R.id.pasteandgo).setVisible(false); menu.findItem(R.id.paste).setVisible(false); } @@ -291,16 +342,106 @@ public class BrowserToolbar extends Geck menu.findItem(R.id.copyurl).setVisible(false); menu.findItem(R.id.share).setVisible(false); menu.findItem(R.id.add_to_launcher).setVisible(false); menu.findItem(R.id.subscribe).setVisible(false); } } }); + mUrlEditText.addTextChangedListener(this); + + mUrlEditText.setOnKeyPreImeListener(new CustomEditText.OnKeyPreImeListener() { + @Override + public boolean onKeyPreIme(View v, int keyCode, KeyEvent event) { + // We only want to process one event per tap + if (event.getAction() != KeyEvent.ACTION_DOWN) + return false; + + if (keyCode == KeyEvent.KEYCODE_ENTER) { + // If the edit text has a composition string, don't submit the text yet. + // ENTER is needed to commit the composition string. + Editable content = mUrlEditText.getText(); + if (!hasCompositionString(content)) { + if (mCommitListener != null) { + mCommitListener.onCommit(); + } + return true; + } + } + + if (keyCode == KeyEvent.KEYCODE_BACK) { + clearFocus(); + return true; + } + + return false; + } + }); + + mUrlEditText.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_ENTER || GamepadUtils.isActionKey(event)) { + if (event.getAction() != KeyEvent.ACTION_DOWN) + return true; + + if (mCommitListener != null) { + mCommitListener.onCommit(); + } + return true; + } else if (GamepadUtils.isBackKey(event)) { + if (mDismissListener != null) { + mDismissListener.onDismiss(); + } + return true; + } + + return false; + } + }); + + mUrlEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (v == null) { + return; + } + + setSelected(hasFocus); + if (hasFocus) { + return; + } + + InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); + try { + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + } catch (NullPointerException e) { + Log.e(LOGTAG, "InputMethodManagerService, why are you throwing" + + " a NullPointerException? See bug 782096", e); + } + } + }); + + mUrlEditText.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (Build.VERSION.SDK_INT >= 11) { + CustomEditText text = (CustomEditText) v; + + if (text.getSelectionStart() == text.getSelectionEnd()) + return false; + + return false; + } + + return false; + } + }); + mTabs.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { toggleTabs(); } }); mTabs.setImageLevel(0); @@ -355,16 +496,27 @@ public class BrowserToolbar extends Geck public void onClick(View v) { Tab tab = Tabs.getInstance().getSelectedTab(); if (tab != null) tab.doStop(); setProgressVisibility(false); } }); + mGo = (ImageButton) findViewById(R.id.go); + mGo.setOnClickListener(new Button.OnClickListener() { + @Override + public void onClick(View v) { + if (mCommitListener != null) { + mCommitListener.onCommit(); + } + } + }); + + mShadow = (ImageView) findViewById(R.id.shadow); mShadow.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { } }); float slideWidth = getResources().getDimension(R.dimen.browser_toolbar_lock_width); @@ -411,16 +563,64 @@ public class BrowserToolbar extends Geck Rect bounds = new Rect(0, 0, tail, height); TailTouchDelegate delegate = new TailTouchDelegate(bounds, mShadow); mTabs.setTouchDelegate(delegate); } }); } } + public boolean onKey(int keyCode, KeyEvent event) { + if (event.getAction() != KeyEvent.ACTION_DOWN) { + return false; + } + + // Galaxy Note sends key events for the stylus that are outside of the + // valid keyCode range (see bug 758427) + if (keyCode > KeyEvent.getMaxKeyCode()) { + return true; + } + + // This method is called only if the key event was not handled + // by any of the views, which usually means the edit box lost focus + if (keyCode == KeyEvent.KEYCODE_BACK || + keyCode == KeyEvent.KEYCODE_MENU || + keyCode == KeyEvent.KEYCODE_DPAD_UP || + keyCode == KeyEvent.KEYCODE_DPAD_DOWN || + keyCode == KeyEvent.KEYCODE_DPAD_LEFT || + keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || + keyCode == KeyEvent.KEYCODE_DPAD_CENTER || + keyCode == KeyEvent.KEYCODE_DEL || + keyCode == KeyEvent.KEYCODE_VOLUME_UP || + keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + return false; + } else if (isEditing()) { + final int prevSelStart = mUrlEditText.getSelectionStart(); + final int prevSelEnd = mUrlEditText.getSelectionEnd(); + + // Manually dispatch the key event to the edit text. If selection changed as + // a result of the key event, then give focus back to mUrlEditText + mUrlEditText.dispatchKeyEvent(event); + + final int curSelStart = mUrlEditText.getSelectionStart(); + final int curSelEnd = mUrlEditText.getSelectionEnd(); + + if (prevSelStart != curSelStart || prevSelEnd != curSelEnd) { + mUrlEditText.requestFocusFromTouch(); + + // Restore the selection, which gets lost due to the focus switch + mUrlEditText.setSelection(curSelStart, curSelEnd); + } + + return true; + } + + return false; + } + @Override public boolean onTouchEvent(MotionEvent event) { // If the motion event has occured below the toolbar (due to the scroll // offset), let it pass through to the page. if (event != null && event.getY() > getHeight() - getScrollY()) { return false; } @@ -510,16 +710,88 @@ public class BrowserToolbar extends Geck case READER_ENABLED: if (Tabs.getInstance().isSelectedTab(tab)) { setPageActionVisibility(mStop.getVisibility() == View.VISIBLE); } break; } } + // Return early if we're backspacing through the string, or + // have no autocomplete results + @Override + public void onAutocomplete(final String result) { + final String text = mUrlEditText.getText().toString(); + + if (result == null) { + mAutoCompleteResult = ""; + return; + } + + if (!result.startsWith(text) || text.equals(result)) { + return; + } + + mAutoCompleteResult = result; + mUrlEditText.getText().append(result.substring(text.length())); + mUrlEditText.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 (TextUtils.isEmpty(mAutoCompleteResult) || !mAutoCompleteResult.equals(text)) { + if (isEditing() && mFilterListener != null) { + mFilterListener.onFilter(text, useHandler ? this : null); + } + mAutoCompletePrefix = text; + + if (reuseAutocomplete) { + onAutocomplete(mAutoCompleteResult); + } + } + + // If the edit text has a composition string, don't call updateGoButton(). + // That method resets IME and composition state will be broken. + if (!hasCompositionString(s) || + InputMethods.isGestureKeyboard(mUrlEditText.getContext())) { + updateGoButton(text); + } + } + + @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 + } + public boolean isVisible() { return getScrollY() == 0; } public void setNextFocusDownId(int nextId) { super.setNextFocusDownId(nextId); mTabs.setNextFocusDownId(nextId); mBack.setNextFocusDownId(nextId); @@ -554,200 +826,35 @@ public class BrowserToolbar extends Geck @Override public void onAnimationEnd(Animation animation) { if (animation.equals(mTitleSlideRight)) { mSiteSecurity.startAnimation(mLockFadeIn); } } - private int getAwesomeBarEntryTranslation() { - return getWidth() - mAwesomeBarEntry.getRight(); + private int getUrlBarEntryTranslation() { + return getWidth() - mUrlBarEntry.getRight(); } - private int getAwesomeBarCurveTranslation() { + private int getUrlBarCurveTranslation() { return getWidth() - mTabs.getLeft(); } - public void fromAwesomeBarSearch(String url) { - // Update the title with the url that was just entered. Don't update the title if - // the AwesomeBar activity was cancelled, or if the user entered an empty string. - if (url != null && url.length() > 0) { - setTitle(url); - } - - if (HardwareUtils.isTablet() || Build.VERSION.SDK_INT < 11) { - return; - } - - // If the awesomebar entry is not selected at this point, this means that - // we had to reinflate the toolbar layout for some reason (device rotation - // while in awesome screen, activity was killed in background, etc). In this - // case, we have to ensure the toolbar is in the correct initial state to - // shrink back. - if (!isSelected()) { - // Keep the entry highlighted during the animation - setSelected(true); - - final int entryTranslation = getAwesomeBarEntryTranslation(); - final int curveTranslation = getAwesomeBarCurveTranslation(); - - if (mAwesomeBarRightEdge != null) { - ViewHelper.setTranslationX(mAwesomeBarRightEdge, entryTranslation); - } - - ViewHelper.setTranslationX(mTabs, curveTranslation); - ViewHelper.setTranslationX(mTabsCounter, curveTranslation); - ViewHelper.setTranslationX(mActionItemBar, curveTranslation); - - if (mHasSoftMenuButton) { - ViewHelper.setTranslationX(mMenu, curveTranslation); - ViewHelper.setTranslationX(mMenuIcon, curveTranslation); - } - - ViewHelper.setAlpha(mPageActionLayout, 0); - ViewHelper.setAlpha(mStop, 0); - } - - final PropertyAnimator contentAnimator = new PropertyAnimator(250); - contentAnimator.setUseHardwareLayer(false); - - // Shrink the awesome entry back to its original size - - if (mAwesomeBarRightEdge != null) { - contentAnimator.attach(mAwesomeBarRightEdge, - PropertyAnimator.Property.TRANSLATION_X, - 0); - } - - contentAnimator.attach(mTabs, - PropertyAnimator.Property.TRANSLATION_X, - 0); - contentAnimator.attach(mTabsCounter, - PropertyAnimator.Property.TRANSLATION_X, - 0); - contentAnimator.attach(mActionItemBar, - PropertyAnimator.Property.TRANSLATION_X, - 0); - - if (mHasSoftMenuButton) { - contentAnimator.attach(mMenu, - PropertyAnimator.Property.TRANSLATION_X, - 0); - contentAnimator.attach(mMenuIcon, - PropertyAnimator.Property.TRANSLATION_X, - 0); - } - - contentAnimator.setPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { - @Override - public void onPropertyAnimationStart() { + private static boolean hasCompositionString(Editable content) { + Object[] spans = content.getSpans(0, content.length(), Object.class); + if (spans != null) { + for (Object span : spans) { + if ((content.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) { + // Found composition string. + return true; + } } - - @Override - public void onPropertyAnimationEnd() { - // Turn off selected state on the entry - setSelected(false); - - PropertyAnimator buttonsAnimator = new PropertyAnimator(300); - - // Fade toolbar buttons (reader, stop) after the entry - // is schrunk back to its original size. - buttonsAnimator.attach(mPageActionLayout, - PropertyAnimator.Property.ALPHA, - 1); - buttonsAnimator.attach(mStop, - PropertyAnimator.Property.ALPHA, - 1); - - buttonsAnimator.start(); - - mAnimatingEntry = false; - - // Trigger animation to update the tabs counter once the - // tabs button is back on screen. - updateTabCount(Tabs.getInstance().getDisplayCount()); - } - }); - - mAnimatingEntry = true; - - postDelayed(new Runnable() { - @Override - public void run() { - contentAnimator.start(); - } - }, 500); - } - - private void onAwesomeBarSearch() { - // This animation doesn't make much sense in a sidebar UI - if (HardwareUtils.isTablet() || Build.VERSION.SDK_INT < 11) { - mActivity.onSearchRequested(); - return; } - - if (mAnimatingEntry) - return; - - final PropertyAnimator contentAnimator = new PropertyAnimator(250); - contentAnimator.setUseHardwareLayer(false); - - final int entryTranslation = getAwesomeBarEntryTranslation(); - final int curveTranslation = getAwesomeBarCurveTranslation(); - - // Keep the entry highlighted during the animation - setSelected(true); - - // Hide stop/reader buttons immediately - ViewHelper.setAlpha(mPageActionLayout, 0); - ViewHelper.setAlpha(mStop, 0); - - // Slide the right side elements of the toolbar - - if (mAwesomeBarRightEdge != null) { - contentAnimator.attach(mAwesomeBarRightEdge, - PropertyAnimator.Property.TRANSLATION_X, - entryTranslation); - } - - contentAnimator.attach(mTabs, - PropertyAnimator.Property.TRANSLATION_X, - curveTranslation); - contentAnimator.attach(mTabsCounter, - PropertyAnimator.Property.TRANSLATION_X, - curveTranslation); - contentAnimator.attach(mActionItemBar, - PropertyAnimator.Property.TRANSLATION_X, - curveTranslation); - - if (mHasSoftMenuButton) { - contentAnimator.attach(mMenu, - PropertyAnimator.Property.TRANSLATION_X, - curveTranslation); - contentAnimator.attach(mMenuIcon, - PropertyAnimator.Property.TRANSLATION_X, - curveTranslation); - } - - contentAnimator.setPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { - @Override - public void onPropertyAnimationStart() { - } - - @Override - public void onPropertyAnimationEnd() { - // Once the entry is fully expanded, start awesome screen - mActivity.onSearchRequested(); - mAnimatingEntry = false; - } - }); - - mAnimatingEntry = true; - contentAnimator.start(); + return false; } private void addTab() { mActivity.addTab(); } private void toggleTabs() { if (mActivity.areTabsShown()) { @@ -764,22 +871,42 @@ public class BrowserToolbar extends Geck if (!tab.isPrivate()) mActivity.showNormalTabs(); else mActivity.showPrivateTabs(); } } } - public void updateTabCount(int count) { - // If toolbar is selected, this means the entry is expanded and the + public void updateTabCountAndAnimate(int count) { + // Don't animate if the toolbar is hidden. + if (!isVisible()) { + updateTabCount(count); + return; + } + + // If toolbar is in edit mode, this means the entry is expanded and the // tabs button is translated offscreen. Don't trigger tabs counter // updates until the tabs button is back on screen. - // See fromAwesomeBarSearch() - if (isSelected()) { + // See stopEditing() + if (!isEditing()) { + mTabsCounter.setCount(count); + + mTabs.setContentDescription((count > 1) ? + mActivity.getString(R.string.num_tabs, count) : + mActivity.getString(R.string.one_tab)); + } + } + + public void updateTabCount(int count) { + // If toolbar is in edit mode, this means the entry is expanded and the + // tabs button is translated offscreen. Don't trigger tabs counter + // updates until the tabs button is back on screen. + // See stopEditing() + if (isEditing()) { return; } // Set TabCounter based on visibility if (isVisible() && ViewHelper.getAlpha(mTabsCounter) != 0) { mTabsCounter.setCountWithAnimation(count); } else { mTabsCounter.setCount(count); @@ -914,39 +1041,57 @@ public class BrowserToolbar extends Geck public void setShadowVisibility(boolean visible) { Tab tab = Tabs.getInstance().getSelectedTab(); if (tab == null) { return; } String url = tab.getURL(); - // Only set shadow to visible when not on about screens except about:blank. - visible &= !(url == null || (url.startsWith("about:") && - !url.equals("about:blank"))); + // Only set shadow to visible when not on about screens (except about:blank) + // and when not in editing mode. + visible &= !(url == null || (url.startsWith("about:") && + !url.equals("about:blank"))) && !isEditing(); if ((mShadow.getVisibility() == View.VISIBLE) != visible) { mShadow.setVisibility(visible ? View.VISIBLE : View.GONE); } } + public void onEditSuggestion(String suggestion) { + if (!isEditing()) { + return; + } + + mUrlEditText.setText(suggestion); + mUrlEditText.setSelection(mUrlEditText.getText().length()); + mUrlEditText.requestFocus(); + + showSoftInput(); + } + private void setTitle(CharSequence title) { mTitle.setText(title); setContentDescription(title != null ? title : mTitle.getHint()); } // Sets the toolbar title according to the selected tab, obeying the mShowUrl prference. private void updateTitle() { Tab tab = Tabs.getInstance().getSelectedTab(); // Keep the title unchanged if there's no selected tab, or if the tab is entering reader mode. if (tab == null || tab.isEnteringReaderMode()) { return; } String url = tab.getURL(); + + if (!isEditing()) { + mUrlEditText.setText(url); + } + // Setting a null title will ensure we just see the "Enter Search or Address" placeholder text. if ("about:home".equals(url) || "about:privatebrowsing".equals(url)) { setTitle(null); return; } // Show the about:blocked page title in red, regardless of prefs if (tab.getErrorType() == Tab.ErrorType.BLOCKED) { @@ -1022,16 +1167,401 @@ public class BrowserToolbar extends Geck ViewHelper.setAlpha(mTabsCounter, 0.0f); if (mHasSoftMenuButton && !HardwareUtils.isTablet()) { ViewHelper.setAlpha(mMenuIcon, 0.0f); } } + public void finishTabsAnimation(boolean tabsAreShown) { + if (tabsAreShown) { + return; + } + + PropertyAnimator animator = new PropertyAnimator(150); + + animator.attach(mTabsCounter, + PropertyAnimator.Property.ALPHA, + 1.0f); + + if (mHasSoftMenuButton && !HardwareUtils.isTablet()) { + animator.attach(mMenuIcon, + PropertyAnimator.Property.ALPHA, + 1.0f); + } + + animator.start(); + } + + public void setOnActivateListener(OnActivateListener listener) { + mActivateListener = listener; + } + + public void setOnCommitListener(OnCommitListener listener) { + mCommitListener = listener; + } + + public void setOnDismissListener(OnDismissListener listener) { + mDismissListener = listener; + } + + public void setOnFilterListener(OnFilterListener listener) { + mFilterListener = listener; + } + + private void showSoftInput() { + InputMethodManager imm = + (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mUrlEditText, InputMethodManager.SHOW_IMPLICIT); + } + + private void showUrlEditContainer() { + setUrlEditContainerVisibility(true, null); + } + + private void showUrlEditContainer(PropertyAnimator animator) { + setUrlEditContainerVisibility(true, animator); + } + + private void hideUrlEditContainer() { + setUrlEditContainerVisibility(false, null); + } + + private void hideUrlEditContainer(PropertyAnimator animator) { + setUrlEditContainerVisibility(false, animator); + } + + private void setUrlEditContainerVisibility(final boolean showEditContainer, PropertyAnimator animator) { + final View viewToShow = (showEditContainer ? mUrlEditContainer : mUrlDisplayContainer); + final View viewToHide = (showEditContainer ? mUrlDisplayContainer : mUrlEditContainer); + + if (animator == null) { + viewToHide.setVisibility(View.GONE); + viewToShow.setVisibility(View.VISIBLE); + + if (showEditContainer) { + mUrlEditText.requestFocus(); + showSoftInput(); + } + + return; + } + + ViewHelper.setAlpha(viewToShow, 0.0f); + animator.attach(viewToShow, + PropertyAnimator.Property.ALPHA, + 1.0f); + + animator.attach(viewToHide, + PropertyAnimator.Property.ALPHA, + 0.0f); + + animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { + @Override + public void onPropertyAnimationStart() { + viewToShow.setVisibility(View.VISIBLE); + + if (showEditContainer) { + ViewHelper.setAlpha(mGo, 0.0f); + mUrlEditText.requestFocus(); + } + } + + @Override + public void onPropertyAnimationEnd() { + viewToHide.setVisibility(View.GONE); + ViewHelper.setAlpha(viewToHide, 1.0f); + + if (showEditContainer) { + ViewHelper.setAlpha(mGo, 1.0f); + showSoftInput(); + } + } + }); + } + + /** + * Returns whether or not the URL bar is in editing mode (url bar is expanded, hiding the new + * tab button). Note that selection state is independent of editing mode. + */ + public boolean isEditing() { + return mIsEditing; + } + + public void startEditing(String url, PropertyAnimator animator) { + if (isEditing()) { + return; + } + + mUrlEditText.setText(url != null ? url : ""); + mIsEditing = true; + + final int entryTranslation = getUrlBarEntryTranslation(); + final int curveTranslation = getUrlBarCurveTranslation(); + + // This animation doesn't make much sense in a sidebar UI + if (HardwareUtils.isTablet() || Build.VERSION.SDK_INT < 11) { + showUrlEditContainer(); + + if (!HardwareUtils.isTablet()) { + if (mUrlBarRightEdge != null) { + ViewHelper.setTranslationX(mUrlBarRightEdge, entryTranslation); + } + + ViewHelper.setTranslationX(mTabs, curveTranslation); + ViewHelper.setTranslationX(mTabsCounter, curveTranslation); + ViewHelper.setTranslationX(mActionItemBar, curveTranslation); + + if (mHasSoftMenuButton) { + ViewHelper.setTranslationX(mMenu, curveTranslation); + ViewHelper.setTranslationX(mMenuIcon, curveTranslation); + } + } + + return; + } + + if (mAnimatingEntry) + return; + + // Highlight the toolbar from the start of the animation. + setSelected(true); + + // Hide page actions/stop buttons immediately + ViewHelper.setAlpha(mPageActionLayout, 0); + ViewHelper.setAlpha(mStop, 0); + + // Slide the right side elements of the toolbar + + if (mUrlBarRightEdge != null) { + animator.attach(mUrlBarRightEdge, + PropertyAnimator.Property.TRANSLATION_X, + entryTranslation); + } + + animator.attach(mTabs, + PropertyAnimator.Property.TRANSLATION_X, + curveTranslation); + animator.attach(mTabsCounter, + PropertyAnimator.Property.TRANSLATION_X, + curveTranslation); + animator.attach(mActionItemBar, + PropertyAnimator.Property.TRANSLATION_X, + curveTranslation); + + if (mHasSoftMenuButton) { + animator.attach(mMenu, + PropertyAnimator.Property.TRANSLATION_X, + curveTranslation); + + animator.attach(mMenuIcon, + PropertyAnimator.Property.TRANSLATION_X, + curveTranslation); + } + + showUrlEditContainer(animator); + + animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { + @Override + public void onPropertyAnimationStart() { + } + + @Override + public void onPropertyAnimationEnd() { + mAnimatingEntry = false; + } + }); + + mAnimatingEntry = true; + } + + /** + * Exits edit mode without updating the toolbar title. + * + * @return the url that was entered + */ + public String cancelEdit() { + return stopEditing(); + } + + /** + * Exits edit mode, updating the toolbar title with the url that was just entered. + * + * @return the url that was entered + */ + public String commitEdit() { + final String url = stopEditing(); + if (!TextUtils.isEmpty(url)) { + setTitle(url); + } + return url; + } + + private String stopEditing() { + final String url = mUrlEditText.getText().toString(); + if (!isEditing()) { + return url; + } + mIsEditing = false; + + if (HardwareUtils.isTablet() || Build.VERSION.SDK_INT < 11) { + hideUrlEditContainer(); + updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount()); + + if (!HardwareUtils.isTablet()) { + if (mUrlBarRightEdge != null) { + ViewHelper.setTranslationX(mUrlBarRightEdge, 0); + } + + ViewHelper.setTranslationX(mTabs, 0); + ViewHelper.setTranslationX(mTabsCounter, 0); + ViewHelper.setTranslationX(mActionItemBar, 0); + + if (mHasSoftMenuButton) { + ViewHelper.setTranslationX(mMenu, 0); + ViewHelper.setTranslationX(mMenuIcon, 0); + } + } + + return url; + } + + final PropertyAnimator contentAnimator = new PropertyAnimator(250); + contentAnimator.setUseHardwareLayer(false); + + // Shrink the urlbar entry back to its original size + + if (mUrlBarRightEdge != null) { + contentAnimator.attach(mUrlBarRightEdge, + PropertyAnimator.Property.TRANSLATION_X, + 0); + } + + contentAnimator.attach(mTabs, + PropertyAnimator.Property.TRANSLATION_X, + 0); + contentAnimator.attach(mTabsCounter, + PropertyAnimator.Property.TRANSLATION_X, + 0); + contentAnimator.attach(mActionItemBar, + PropertyAnimator.Property.TRANSLATION_X, + 0); + + if (mHasSoftMenuButton) { + contentAnimator.attach(mMenu, + PropertyAnimator.Property.TRANSLATION_X, + 0); + + contentAnimator.attach(mMenuIcon, + PropertyAnimator.Property.TRANSLATION_X, + 0); + } + + hideUrlEditContainer(contentAnimator); + + contentAnimator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { + @Override + public void onPropertyAnimationStart() { + } + + @Override + public void onPropertyAnimationEnd() { + setShadowVisibility(true); + + PropertyAnimator buttonsAnimator = new PropertyAnimator(300); + + // Fade toolbar buttons (page actions, stop) after the entry + // is schrunk back to its original size. + buttonsAnimator.attach(mPageActionLayout, + PropertyAnimator.Property.ALPHA, + 1); + buttonsAnimator.attach(mStop, + PropertyAnimator.Property.ALPHA, + 1); + + buttonsAnimator.start(); + + mAnimatingEntry = false; + + // Trigger animation to update the tabs counter once the + // tabs button is back on screen. + updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount()); + } + }); + + mAnimatingEntry = true; + contentAnimator.start(); + + return url; + } + + private void updateGoButton(String text) { + if (text.length() == 0) { + mGo.setVisibility(View.GONE); + return; + } + + mGo.setVisibility(View.VISIBLE); + + int imageResource = R.drawable.ic_url_bar_go; + String contentDescription = mActivity.getString(R.string.go); + int imeAction = EditorInfo.IME_ACTION_GO; + + int actionBits = mUrlEditText.getImeOptions() & EditorInfo.IME_MASK_ACTION; + if (StringUtils.isSearchQuery(text, actionBits == EditorInfo.IME_ACTION_SEARCH)) { + imageResource = R.drawable.ic_url_bar_search; + contentDescription = mActivity.getString(R.string.search); + imeAction = EditorInfo.IME_ACTION_SEARCH; + } + + InputMethodManager imm = InputMethods.getInputMethodManager(mUrlEditText.getContext()); + if (imm == null) { + return; + } + boolean restartInput = false; + if (actionBits != imeAction) { + int optionBits = mUrlEditText.getImeOptions() & ~EditorInfo.IME_MASK_ACTION; + mUrlEditText.setImeOptions(optionBits | imeAction); + + mDelayRestartInput = (imeAction == EditorInfo.IME_ACTION_GO) && + (InputMethods.shouldDelayUrlBarUpdate(mUrlEditText.getContext())); + if (!mDelayRestartInput) { + restartInput = true; + } + } else if (mDelayRestartInput) { + // Only call delayed restartInput when actionBits == imeAction + // so if there are two restarts in a row, the first restarts will + // be discarded and the second restart will be properly delayed + mDelayRestartInput = false; + restartInput = true; + } + if (restartInput) { + updateKeyboardInputType(); + imm.restartInput(mUrlEditText); + mGo.setImageResource(imageResource); + mGo.setContentDescription(contentDescription); + } + } + + private void updateKeyboardInputType() { + // If the user enters a space, then we know they are entering search terms, not a URL. + // We can then switch to text mode so, + // 1) the IME auto-inserts spaces between words + // 2) the IME doesn't reset input keyboard to Latin keyboard. + String text = mUrlEditText.getText().toString(); + int currentInputType = mUrlEditText.getInputType(); + int newInputType = StringUtils.isSearchQuery(text, false) + ? (currentInputType & ~InputType.TYPE_TEXT_VARIATION_URI) // Text mode + : (currentInputType | InputType.TYPE_TEXT_VARIATION_URI); // URL mode + if (newInputType != currentInputType) { + mUrlEditText.setRawInputType(newInputType); + } + } + public void updateBackButton(boolean enabled) { Drawable drawable = mBack.getDrawable(); if (drawable != null) drawable.setAlpha(enabled ? 255 : 77); mBack.setEnabled(enabled); } @@ -1045,50 +1575,58 @@ public class BrowserToolbar extends Geck if (mForward.getVisibility() != View.VISIBLE) return; // We want the forward button to show immediately when switching tabs mForwardAnim = new PropertyAnimator(mSwitchingTabs ? 10 : FORWARD_ANIMATION_DURATION); final int width = mForward.getWidth() / 2; - mForwardAnim.setPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { + mForwardAnim.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { @Override public void onPropertyAnimationStart() { if (!enabled) { // Set the margin before the transition when hiding the forward button. We // have to do this so that the favicon isn't clipped during the transition ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)mUrlDisplayContainer.getLayoutParams(); layoutParams.leftMargin = 0; - mUrlDisplayContainer.requestLayout(); + + // Do the same on the URL edit container + layoutParams = (ViewGroup.MarginLayoutParams)mUrlEditContainer.getLayoutParams(); + layoutParams.leftMargin = 0; + + requestLayout(); // Note, we already translated the favicon, site security, and text field // in prepareForwardAnimation, so they should appear to have not moved at // all at this point. } } @Override public void onPropertyAnimationEnd() { if (enabled) { ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)mUrlDisplayContainer.getLayoutParams(); - layoutParams.leftMargin = mAddressBarViewOffset; + layoutParams.leftMargin = mUrlBarViewOffset; + + layoutParams = (ViewGroup.MarginLayoutParams)mUrlEditContainer.getLayoutParams(); + layoutParams.leftMargin = mUrlBarViewOffset; ViewHelper.setTranslationX(mTitle, 0); ViewHelper.setTranslationX(mFavicon, 0); ViewHelper.setTranslationX(mSiteSecurity, 0); } ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)mForward.getLayoutParams(); layoutParams.leftMargin = mDefaultForwardMargin + (mForward.isEnabled() ? width : 0); ViewHelper.setTranslationX(mForward, 0); - mUrlDisplayContainer.requestLayout(); + requestLayout(); mForwardAnim = null; } }); prepareForwardAnimation(mForwardAnim, enabled, width); mForwardAnim.start(); } @@ -1108,35 +1646,35 @@ public class BrowserToolbar extends Geck 0); anim.attach(mSiteSecurity, PropertyAnimator.Property.TRANSLATION_X, 0); // We're hiding the forward button. We're going to reset the margin before // the animation starts, so we shift these items to the right so that they don't // appear to move initially. - ViewHelper.setTranslationX(mTitle, mAddressBarViewOffset); - ViewHelper.setTranslationX(mFavicon, mAddressBarViewOffset); - ViewHelper.setTranslationX(mSiteSecurity, mAddressBarViewOffset); + ViewHelper.setTranslationX(mTitle, mUrlBarViewOffset); + ViewHelper.setTranslationX(mFavicon, mUrlBarViewOffset); + ViewHelper.setTranslationX(mSiteSecurity, mUrlBarViewOffset); } else { anim.attach(mForward, PropertyAnimator.Property.TRANSLATION_X, width); anim.attach(mForward, PropertyAnimator.Property.ALPHA, 1); anim.attach(mTitle, PropertyAnimator.Property.TRANSLATION_X, - mAddressBarViewOffset); + mUrlBarViewOffset); anim.attach(mFavicon, PropertyAnimator.Property.TRANSLATION_X, - mAddressBarViewOffset); + mUrlBarViewOffset); anim.attach(mSiteSecurity, PropertyAnimator.Property.TRANSLATION_X, - mAddressBarViewOffset); + mUrlBarViewOffset); } } @Override public void addActionItem(View actionItem) { mActionItemBar.addView(actionItem); } @@ -1161,22 +1699,23 @@ public class BrowserToolbar extends Geck setProgressVisibility(tab.getState() == Tab.STATE_LOADING); setSecurityMode(tab.getSecurityMode()); setPageActionVisibility(mStop.getVisibility() == View.VISIBLE); setShadowVisibility(true); updateBackButton(tab.canDoBack()); updateForwardButton(tab.canDoForward()); final boolean isPrivate = tab.isPrivate(); - mAddressBarBg.setPrivateMode(isPrivate); + mUrlBarBackground.setPrivateMode(isPrivate); setPrivateMode(isPrivate); mTabs.setPrivateMode(isPrivate); mTitle.setPrivateMode(isPrivate); mMenu.setPrivateMode(isPrivate); mMenuIcon.setPrivateMode(isPrivate); + mUrlEditText.setPrivateMode(isPrivate); if (mBack instanceof BackButton) ((BackButton) mBack).setPrivateMode(isPrivate); if (mForward instanceof ForwardButton) ((ForwardButton) mForward).setPrivateMode(isPrivate); } }
--- a/mobile/android/base/BrowserToolbarBackground.java +++ b/mobile/android/base/BrowserToolbarBackground.java @@ -32,11 +32,11 @@ public class BrowserToolbarBackground ex stateList.addState(new int[] { R.attr.state_private }, new ColorDrawable(mActivity.getResources().getColor(R.color.background_private))); stateList.addState(new int[] {}, drawable); setBackgroundDrawable(stateList); } @Override public void onLightweightThemeReset() { - setBackgroundResource(R.drawable.address_bar_bg); + setBackgroundResource(R.drawable.url_bar_bg); } }
--- a/mobile/android/base/Favicons.java +++ b/mobile/android/base/Favicons.java @@ -238,20 +238,20 @@ public class Favicons { color = BitmapUtils.getDominantColor(image); mColorCache.put(key, color); return color; } public void attachToContext(Context context) { mContext = context; if (sFaviconSmallSize < 0) { - sFaviconSmallSize = Math.round(mContext.getResources().getDimension(R.dimen.awesomebar_row_favicon_size_small)); + sFaviconSmallSize = Math.round(mContext.getResources().getDimension(R.dimen.favicon_size_small)); } if (sFaviconLargeSize < 0) { - sFaviconLargeSize = Math.round(mContext.getResources().getDimension(R.dimen.awesomebar_row_favicon_size_large)); + sFaviconLargeSize = Math.round(mContext.getResources().getDimension(R.dimen.favicon_size_large)); } } private class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> { private long mId; private String mPageUrl; private String mFaviconUrl; private OnFaviconLoadedListener mListener;
--- a/mobile/android/base/ForwardButton.java +++ b/mobile/android/base/ForwardButton.java @@ -61,17 +61,17 @@ public class ForwardButton extends Shape @Override public void draw(Canvas canvas) { super.draw(canvas); // Draw the border on top. canvas.drawPath(mBorderPath, isPrivateMode() ? mBorderPrivatePaint : mBorderPaint); } - // The drawable is constructed as per @drawable/address_bar_nav_button. + // The drawable is constructed as per @drawable/url_bar_nav_button. @Override public void onLightweightThemeChanged() { Drawable drawable = mActivity.getLightweightTheme().getDrawable(this); if (drawable == null) return; Resources resources = getContext().getResources(); StateListDrawable stateList = new StateListDrawable(); @@ -83,11 +83,11 @@ public class ForwardButton extends Shape stateList.addState(new int[] { R.attr.state_private }, new ColorDrawable(resources.getColor(R.color.background_private))); stateList.addState(new int[] {}, drawable); setBackgroundDrawable(stateList); } @Override public void onLightweightThemeReset() { - setBackgroundResource(R.drawable.address_bar_nav_button); + setBackgroundResource(R.drawable.url_bar_nav_button); } }
--- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -562,18 +562,16 @@ abstract public class GeckoApp } } else if (event.equals("log")) { // generic log listener final String msg = message.getString("msg"); Log.d(LOGTAG, "Log: " + msg); } else if (event.equals("Reader:FaviconRequest")) { final String url = message.getString("url"); handleFaviconRequest(url); - } else if (event.equals("Reader:GoToReadingList")) { - showReadingList(); } else if (event.equals("Gecko:Ready")) { mGeckoReadyStartupTimer.stop(); geckoConnected(); // This method is already running on the background thread, so we // know that mHealthRecorder will exist. That doesn't stop us being // paranoid. // This method is cheap, so don't spawn a new runnable. @@ -1474,17 +1472,16 @@ abstract public class GeckoApp //register for events registerEventListener("log"); registerEventListener("Reader:ListCountRequest"); registerEventListener("Reader:Added"); registerEventListener("Reader:Removed"); registerEventListener("Reader:Share"); registerEventListener("Reader:FaviconRequest"); - registerEventListener("Reader:GoToReadingList"); registerEventListener("onCameraCapture"); registerEventListener("Menu:Add"); registerEventListener("Menu:Remove"); registerEventListener("Menu:Update"); registerEventListener("Gecko:Ready"); registerEventListener("Toast:Show"); registerEventListener("DOMFullScreen:Start"); registerEventListener("DOMFullScreen:Stop"); @@ -2023,17 +2020,16 @@ abstract public class GeckoApp public void onDestroy() { unregisterEventListener("log"); unregisterEventListener("Reader:ListCountRequest"); unregisterEventListener("Reader:Added"); unregisterEventListener("Reader:Removed"); unregisterEventListener("Reader:Share"); unregisterEventListener("Reader:FaviconRequest"); - unregisterEventListener("Reader:GoToReadingList"); unregisterEventListener("onCameraCapture"); unregisterEventListener("Menu:Add"); unregisterEventListener("Menu:Remove"); unregisterEventListener("Menu:Update"); unregisterEventListener("Gecko:Ready"); unregisterEventListener("Toast:Show"); unregisterEventListener("DOMFullScreen:Start"); unregisterEventListener("DOMFullScreen:Stop"); @@ -2256,28 +2252,23 @@ abstract public class GeckoApp } } } public PromptService getPromptService() { return mPromptService; } - public void showReadingList() { - Intent intent = new Intent(getBaseContext(), AwesomeBar.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_NO_HISTORY); - intent.putExtra(AwesomeBar.TARGET_KEY, AwesomeBar.Target.CURRENT_TAB.toString()); - intent.putExtra(AwesomeBar.READING_LIST_KEY, true); - - int requestCode = GeckoAppShell.sActivityHelper.makeRequestCodeForAwesomebar(); - startActivityForResult(intent, requestCode); - } - @Override public void onBackPressed() { + if (getSupportFragmentManager().getBackStackEntryCount() > 0) { + super.onBackPressed(); + return; + } + if (autoHideTabs()) { return; } if (mDoorHangerPopup != null && mDoorHangerPopup.isShowing()) { mDoorHangerPopup.dismiss(); return; }
--- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -692,18 +692,18 @@ public class GeckoAppShell static void createShortcut(String aTitle, String aURI, String aIconData, String aType) { if ("webapp".equals(aType)) { Log.w(LOGTAG, "createShortcut with no unique URI should not be used for aType = webapp!"); } createShortcut(aTitle, aURI, aURI, aIconData, aType); } - // internal, for non-webapps - static void createShortcut(String aTitle, String aURI, Bitmap aBitmap, String aType) { + // for non-webapps + public static void createShortcut(String aTitle, String aURI, Bitmap aBitmap, String aType) { createShortcut(aTitle, aURI, aURI, aBitmap, aType); } // internal, for webapps static void createShortcut(String aTitle, String aURI, String aUniqueURI, String aIconData, String aType) { createShortcut(aTitle, aURI, aUniqueURI, BitmapUtils.getBitmapFromDataURI(aIconData), aType); } @@ -1064,22 +1064,22 @@ public class GeckoAppShell * * @param targetURI the string spec of the URI to open. * @param mimeType an optional MIME type string. * @param action an Android action specifier, such as * <code>Intent.ACTION_SEND</code>. * @param title the title to use in <code>ACTION_SEND</code> intents. * @return true if the activity started successfully; false otherwise. */ - static boolean openUriExternal(String targetURI, - String mimeType, - String packageName, - String className, - String action, - String title) { + public static boolean openUriExternal(String targetURI, + String mimeType, + String packageName, + String className, + String action, + String title) { final Context context = getContext(); final Intent intent = getOpenURIIntent(context, targetURI, mimeType, action, title); if (intent == null) { return false; }
--- a/mobile/android/base/InputMethods.java +++ b/mobile/android/base/InputMethods.java @@ -56,17 +56,17 @@ final class InputMethods { return Build.VERSION.SDK_INT >= 17 && (METHOD_ANDROID_LATINIME.equals(inputMethod) || METHOD_GOOGLE_LATINIME.equals(inputMethod)); } public static boolean shouldCommitCharAsKey(String inputMethod) { return METHOD_HTC_TOUCH_INPUT.equals(inputMethod); } - public static boolean shouldDelayAwesomebarUpdate(Context context) { + public static boolean shouldDelayUrlBarUpdate(Context context) { String inputMethod = getCurrentInputMethod(context); return METHOD_SAMSUNG.equals(inputMethod) || METHOD_SWIFTKEY.equals(inputMethod); } public static boolean isGestureKeyboard(Context context) { // SwiftKey is a gesture keyboard, but it doesn't seem to need any special-casing // to do AwesomeBar auto-spacing.
--- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -49,28 +49,22 @@ FENNEC_JAVA_FILES = \ ActivityHandlerHelper.java \ AlertNotification.java \ AlignRightLinkPreference.java \ AllCapsTextView.java \ AndroidImport.java \ AndroidImportPreference.java \ AnimatedHeightLayout.java \ AppNotificationClient.java \ - AwesomeBar.java \ - AwesomebarResultHandler.java \ - AwesomeBarTabs.java \ + AutocompleteHandler.java \ animation/AnimatorProxy.java \ animation/HeightChangeAnimation.java \ animation/PropertyAnimator.java \ animation/Rotate3DAnimation.java \ animation/ViewHelper.java \ - awesomebar/AwesomeBarTab.java \ - awesomebar/AllPagesTab.java \ - awesomebar/BookmarksTab.java \ - awesomebar/HistoryTab.java \ BackButton.java \ BrowserApp.java \ BrowserToolbar.java \ BrowserToolbarBackground.java \ CameraImageResultHandler.java \ CameraVideoResultHandler.java \ CanvasDelegate.java \ CheckableLinearLayout.java \ @@ -133,33 +127,30 @@ FENNEC_JAVA_FILES = \ PageActionLayout.java \ PrefsHelper.java \ PrivateDataPreference.java \ PrivateTab.java \ Prompt.java \ PromptInput.java \ PromptService.java \ Restarter.java \ - SearchEngine.java \ sqlite/ByteBufferInputStream.java \ sqlite/MatrixBlobCursor.java \ sqlite/SQLiteBridge.java \ sqlite/SQLiteBridgeException.java \ ReaderModeUtils.java \ RemoteTabs.java \ RobocopAPI.java \ - SearchEngineRow.java \ ServiceNotificationClient.java \ ScrollAnimator.java \ SessionParser.java \ ShapedButton.java \ SharedPreferencesHelper.java \ SiteIdentityPopup.java \ SmsManager.java \ - SuggestClient.java \ SurfaceBits.java \ SyncPreference.java \ Tab.java \ TabCounter.java \ Tabs.java \ TabsPanel.java \ TabsTray.java \ TabsAccessor.java \ @@ -219,45 +210,66 @@ FENNEC_JAVA_FILES = \ gfx/SubdocumentScrollHelper.java \ gfx/TextLayer.java \ gfx/TextureGenerator.java \ gfx/TextureReaper.java \ gfx/TileLayer.java \ gfx/TouchEventHandler.java \ gfx/ViewTransform.java \ gfx/VirtualLayer.java \ + home/BookmarksListAdapter.java \ + home/BookmarksListView.java \ + home/BookmarksPage.java \ + home/BookmarkFolderView.java \ + home/BookmarkThumbnailView.java \ + home/BrowserSearch.java \ + home/HistoryPage.java \ + home/HomeCursorLoaderCallbacks.java \ + home/HomeFragment.java \ + home/HomeListView.java \ + home/HomePager.java \ + home/HomePagerTabStrip.java \ + home/FadedTextView.java \ + home/FaviconsLoader.java \ + home/LastTabsPage.java \ + home/MostRecentPage.java \ + home/MostVisitedPage.java \ + home/MultiTypeCursorAdapter.java \ + home/PinBookmarkDialog.java \ + home/ReadingListPage.java \ + home/SearchEngine.java \ + home/SearchEngineRow.java \ + home/SearchLoader.java \ + home/SimpleCursorLoader.java \ + home/SuggestClient.java \ + home/TabMenuStrip.java \ + home/TopBookmarkItemView.java \ + home/TopBookmarksAdapter.java \ + home/TopBookmarksView.java \ + home/TwoLinePageRow.java \ menu/GeckoMenu.java \ menu/GeckoMenuInflater.java \ menu/GeckoMenuItem.java \ menu/GeckoSubMenu.java \ menu/MenuItemActionBar.java \ menu/MenuItemActionView.java \ menu/MenuItemDefault.java \ menu/MenuPanel.java \ menu/MenuPopup.java \ preferences/SearchPreferenceCategory.java \ preferences/SearchEnginePreference.java \ - widget/AboutHome.java \ - widget/AboutHomeView.java \ - widget/AboutHomeSection.java \ widget/ActivityChooserModel.java \ - widget/AddonsSection.java \ widget/ButtonToast.java \ widget/ArrowPopup.java \ widget/DateTimePicker.java \ widget/Divider.java \ widget/FaviconView.java \ widget/GeckoPopupMenu.java \ widget/GeckoActionProvider.java \ widget/IconTabWidget.java \ - widget/LastTabsSection.java \ - widget/LinkTextView.java \ - widget/PromoBox.java \ - widget/RemoteTabsSection.java \ - widget/TopSitesView.java \ widget/TabRow.java \ widget/ThumbnailView.java \ widget/TwoWayView.java \ GeckoNetworkManager.java \ GeckoScreenOrientationListener.java \ UpdateService.java \ GeckoUpdateReceiver.java \ ReferrerReceiver.java \ @@ -436,100 +448,109 @@ ifdef MOZ_ANDROID_SHARED_ID DEFINES += -DMOZ_ANDROID_SHARED_ID="$(MOZ_ANDROID_SHARED_ID)" endif ifdef MOZ_ANDROID_SHARED_ACCOUNT_TYPE DEFINES += -DMOZ_ANDROID_SHARED_ACCOUNT_TYPE="$(MOZ_ANDROID_SHARED_ACCOUNT_TYPE)" endif RES_LAYOUT = \ $(SYNC_RES_LAYOUT) \ - res/layout/abouthome_content.xml \ res/layout/arrow_popup.xml \ res/layout/autocomplete_list.xml \ res/layout/autocomplete_list_item.xml \ - res/layout/awesomebar.xml \ - res/layout/awesomebar_folder_row.xml \ - res/layout/awesomebar_header_row.xml \ - res/layout/awesomebar_allpages_list.xml \ - res/layout/awesomebar_row.xml \ - res/layout/awesomebar_search.xml \ - res/layout/awesomebar_suggestion_prompt.xml \ - res/layout/awesomebar_tab_indicator.xml \ - res/layout/awesomebar_tabs.xml \ res/layout/bookmark_edit.xml \ + res/layout/bookmark_folder_row.xml \ + res/layout/bookmark_item_row.xml \ + res/layout/browser_search.xml \ res/layout/browser_toolbar.xml \ res/layout/datetime_picker.xml \ res/layout/doorhanger.xml \ res/layout/doorhanger_button.xml \ res/layout/find_in_page_content.xml \ res/layout/font_size_preference.xml \ res/layout/gecko_app.xml \ + res/layout/home_bookmarks_page.xml \ + res/layout/home_empty_page.xml \ + res/layout/home_empty_reading_page.xml \ + res/layout/home_item_row.xml \ + res/layout/home_header_row.xml \ + res/layout/home_history_page.xml \ + res/layout/home_history_tabs_indicator.xml \ + res/layout/home_last_tabs_page.xml \ + res/layout/home_history_list.xml \ + res/layout/home_most_recent_page.xml \ + res/layout/home_most_visited_page.xml \ + res/layout/home_pager.xml \ + res/layout/home_reading_list_page.xml \ res/layout/home_search_item_row.xml \ + res/layout/home_suggestion_prompt.xml \ res/layout/web_app.xml \ res/layout/launch_app_list.xml \ res/layout/launch_app_listitem.xml \ res/layout/menu_action_bar.xml \ res/layout/menu_item_action_view.xml \ res/layout/menu_popup.xml \ res/layout/notification_icon_text.xml \ res/layout/notification_progress.xml \ res/layout/notification_progress_text.xml \ + res/layout/pin_bookmark_dialog.xml \ res/layout/preference_rightalign_icon.xml \ res/layout/preference_search_tip.xml \ res/layout/search_engine_row.xml \ res/layout/site_setting_item.xml \ res/layout/site_setting_title.xml \ res/layout/shared_ui_components.xml \ res/layout/site_identity.xml \ res/layout/suggestion_item.xml \ res/layout/remote_tabs_child.xml \ res/layout/remote_tabs_group.xml \ + res/layout/search_engine_row.xml \ + res/layout/tab_menu_strip.xml \ res/layout/tabs_panel.xml \ res/layout/tabs_counter.xml \ res/layout/tabs_panel_header.xml \ res/layout/tabs_panel_indicator.xml \ res/layout/tabs_item_cell.xml \ res/layout/tabs_item_row.xml \ res/layout/text_selection_handles.xml \ + res/layout/top_bookmark_item_view.xml \ + res/layout/two_line_page_row.xml \ res/layout/list_item_header.xml \ res/layout/select_dialog_list.xml \ res/layout/select_dialog_multichoice.xml \ res/layout/select_dialog_singlechoice.xml \ res/layout/simple_dropdown_item_1line.xml \ - res/layout/abouthome_addon_row.xml \ - res/layout/abouthome_last_tabs_row.xml \ - res/layout/abouthome_section.xml \ - res/layout/abouthome_remote_tab_row.xml \ - res/layout/abouthome_topsite_item.xml \ + res/layout/suggestion_item.xml \ res/layout/validation_message.xml \ res/layout/videoplayer.xml \ $(NULL) RES_LAYOUT_LARGE_V11 = \ - res/layout-large-v11/awesomebar_search.xml \ res/layout-large-v11/browser_toolbar.xml \ + res/layout-large-v11/home_pager.xml \ $(NULL) RES_LAYOUT_LARGE_LAND_V11 = \ + res/layout-large-land-v11/home_history_page.xml \ + res/layout-large-land-v11/home_history_tabs_indicator.xml \ + res/layout-large-land-v11/home_history_list.xml \ res/layout-large-land-v11/tabs_panel.xml \ res/layout-large-land-v11/tabs_panel_header.xml \ res/layout-large-land-v11/tabs_panel_footer.xml \ $(NULL) RES_LAYOUT_XLARGE_V11 = \ - res/layout-xlarge-v11/awesomebar_search.xml \ res/layout-xlarge-v11/font_size_preference.xml \ + res/layout-xlarge-v11/home_history_page.xml \ + res/layout-xlarge-v11/home_history_tabs_indicator.xml \ + res/layout-xlarge-v11/home_history_list.xml \ res/layout-xlarge-v11/remote_tabs_child.xml \ res/layout-xlarge-v11/remote_tabs_group.xml \ $(NULL) -RES_LAYOUT_XLARGE_LAND_V11 = \ - res/layout-xlarge-land-v11/abouthome_content.xml \ - $(NULL) - RES_VALUES = \ $(SYNC_RES_VALUES) \ res/values/attrs.xml \ res/values/arrays.xml \ res/values/colors.xml \ res/values/dimens.xml \ res/values/integers.xml \ res/values/layout.xml \ @@ -555,29 +576,39 @@ RES_VALUES_LARGE_V11 = \ $(SYNC_RES_VALUES_LARGE_V11) \ res/values-large-v11/dimens.xml \ res/values-large-v11/layout.xml \ res/values-large-v11/styles.xml \ res/values-large-v11/themes.xml \ $(NULL) RES_VALUES_LARGE_LAND_V11 = \ + res/values-large-land-v11/dimens.xml \ res/values-large-land-v11/styles.xml \ $(NULL) RES_VALUES_XLARGE_V11 = \ res/values-xlarge-v11/dimens.xml \ res/values-xlarge-v11/integers.xml \ res/values-xlarge-v11/styles.xml \ $(NULL) +RES_VALUES_XLARGE_LAND_V11 = \ + res/values-xlarge-land-v11/dimens.xml \ + res/values-xlarge-land-v11/styles.xml \ + $(NULL) + RES_VALUES_V14 = \ res/values-v14/styles.xml \ $(NULL) +RES_VALUES_V16 = \ + res/values-v16/styles.xml \ + $(NULL) + RES_XML = \ res/xml/preferences_display.xml \ res/xml/preferences_search.xml \ res/xml/preferences_privacy.xml \ res/xml/preferences_vendor.xml \ res/xml/preferences_devtools.xml \ $(SYNC_RES_XML) \ $(NULL) @@ -585,98 +616,90 @@ RES_XML = \ RES_XML_V11 = \ res/xml-v11/preferences_customize.xml \ res/xml-v11/preference_headers.xml \ res/xml-v11/preferences_customize_tablet.xml \ res/xml-v11/preferences.xml \ $(NULL) RES_ANIM = \ - res/anim/awesomebar_fade_in.xml \ res/anim/popup_show.xml \ res/anim/popup_hide.xml \ - res/anim/awesomebar_fade_out.xml \ - res/anim/awesomebar_hold_still.xml \ res/anim/grow_fade_in.xml \ res/anim/grow_fade_in_center.xml \ res/anim/progress_spinner.xml \ res/anim/shrink_fade_out.xml \ $(NULL) RES_DRAWABLE_MDPI = \ $(SYNC_RES_DRAWABLE_MDPI) \ res/drawable-mdpi/blank.png \ res/drawable-mdpi/favicon.png \ res/drawable-mdpi/folder.png \ - res/drawable-mdpi/abouthome_icon.png \ - res/drawable-mdpi/abouthome_logo_dark.png \ - res/drawable-mdpi/abouthome_logo_light.png \ - res/drawable-mdpi/abouthome_promo_box_bg.9.png \ - res/drawable-mdpi/abouthome_promo_box_pressed_bg.9.png \ - res/drawable-mdpi/abouthome_promo_logo_apps.png \ - res/drawable-mdpi/abouthome_promo_logo_sync.png \ res/drawable-mdpi/abouthome_thumbnail.png \ - res/drawable-mdpi/abouthome_thumbnail_bg.png \ - res/drawable-mdpi/abouthome_thumbnail_add.png \ - res/drawable-mdpi/address_bar_bg_shadow.png \ res/drawable-mdpi/alert_addon.png \ res/drawable-mdpi/alert_app.png \ res/drawable-mdpi/alert_download.png \ res/drawable-mdpi/alert_camera.png \ res/drawable-mdpi/alert_mic.png \ res/drawable-mdpi/alert_mic_camera.png \ res/drawable-mdpi/arrow_popup_bg.9.png \ res/drawable-mdpi/autocomplete_list_bg.9.png \ - res/drawable-mdpi/awesomebar_tab_center.9.png \ - res/drawable-mdpi/awesomebar_tab_left.9.png \ - res/drawable-mdpi/awesomebar_tab_right.9.png \ - res/drawable-mdpi/awesomebar_sep_left.9.png \ - res/drawable-mdpi/awesomebar_sep_right.9.png \ + res/drawable-mdpi/bookmark_folder_closed.png \ + res/drawable-mdpi/bookmark_folder_opened.png \ res/drawable-mdpi/desktop_notification.png \ - res/drawable-mdpi/ic_addons_empty.png \ - res/drawable-mdpi/ic_awesomebar_go.png \ - res/drawable-mdpi/ic_awesomebar_reader.png \ - res/drawable-mdpi/ic_awesomebar_search.png \ - res/drawable-mdpi/ic_awesomebar_star.png \ - res/drawable-mdpi/ic_awesomebar_tab.png \ + res/drawable-mdpi/home_tab_menu_strip.9.png \ res/drawable-mdpi/ic_menu_addons_filler.png \ res/drawable-mdpi/ic_menu_bookmark_add.png \ res/drawable-mdpi/ic_menu_bookmark_remove.png \ res/drawable-mdpi/ic_menu_character_encoding.png \ res/drawable-mdpi/ic_menu_close_all_tabs.png \ res/drawable-mdpi/ic_menu_forward.png \ res/drawable-mdpi/ic_menu_guest.png \ res/drawable-mdpi/ic_menu_new_private_tab.png \ res/drawable-mdpi/ic_menu_new_tab.png \ res/drawable-mdpi/ic_menu_reload.png \ res/drawable-mdpi/ic_status_logo.png \ + res/drawable-mdpi/ic_url_bar_go.png \ + res/drawable-mdpi/ic_url_bar_reader.png \ + res/drawable-mdpi/ic_url_bar_search.png \ + res/drawable-mdpi/ic_url_bar_star.png \ + res/drawable-mdpi/ic_url_bar_tab.png \ + res/drawable-mdpi/icon_last_tabs.png \ + res/drawable-mdpi/icon_last_tabs_empty.png \ + res/drawable-mdpi/icon_most_recent.png \ + res/drawable-mdpi/icon_most_recent_empty.png \ + res/drawable-mdpi/icon_most_visited.png \ + res/drawable-mdpi/icon_most_visited_empty.png \ res/drawable-mdpi/icon_pageaction.png \ + res/drawable-mdpi/icon_reading_list_empty.png \ res/drawable-mdpi/progress_spinner.png \ res/drawable-mdpi/tab_indicator_divider.9.png \ res/drawable-mdpi/tab_indicator_selected.9.png \ res/drawable-mdpi/tab_indicator_selected_focused.9.png \ res/drawable-mdpi/spinner_default.9.png \ res/drawable-mdpi/spinner_focused.9.png \ res/drawable-mdpi/spinner_pressed.9.png \ res/drawable-mdpi/tab_new.png \ res/drawable-mdpi/tab_new_pb.png \ res/drawable-mdpi/tab_close.png \ res/drawable-mdpi/tab_thumbnail_default.png \ res/drawable-mdpi/tab_thumbnail_shadow.png \ res/drawable-mdpi/tabs_count.png \ res/drawable-mdpi/tabs_count_foreground.png \ + res/drawable-mdpi/url_bar_bg_shadow.png \ + res/drawable-mdpi/url_bar_entry_default.9.png \ + res/drawable-mdpi/url_bar_entry_default_pb.9.png \ + res/drawable-mdpi/url_bar_entry_pressed.9.png \ + res/drawable-mdpi/url_bar_entry_pressed_pb.9.png \ res/drawable-mdpi/tip_addsearch.png \ res/drawable-mdpi/toast.9.png \ res/drawable-mdpi/toast_button_focused.9.png \ res/drawable-mdpi/toast_button_pressed.9.png \ res/drawable-mdpi/toast_divider.9.png \ - res/drawable-mdpi/address_bar_url_default.9.png \ - res/drawable-mdpi/address_bar_url_default_pb.9.png \ - res/drawable-mdpi/address_bar_url_pressed.9.png \ - res/drawable-mdpi/address_bar_url_pressed_pb.9.png \ res/drawable-mdpi/find_close.png \ res/drawable-mdpi/find_next.png \ res/drawable-mdpi/find_prev.png \ res/drawable-mdpi/larry.png \ res/drawable-mdpi/lock_identified.png \ res/drawable-mdpi/lock_verified.png \ res/drawable-mdpi/menu.png \ res/drawable-mdpi/menu_pb.png \ @@ -687,106 +710,104 @@ RES_DRAWABLE_MDPI = \ res/drawable-mdpi/menu_item_check.png \ res/drawable-mdpi/menu_item_more.png \ res/drawable-mdpi/menu_item_uncheck.png \ res/drawable-mdpi/shield.png \ res/drawable-mdpi/shield_doorhanger.png \ res/drawable-mdpi/tabs_normal.png \ res/drawable-mdpi/tabs_private.png \ res/drawable-mdpi/tabs_synced.png \ + res/drawable-mdpi/top_bookmark_add.png \ res/drawable-mdpi/urlbar_stop.png \ res/drawable-mdpi/reader.png \ + res/drawable-mdpi/reader_cropped.png \ res/drawable-mdpi/reader_active.png \ res/drawable-mdpi/reading_list.png \ res/drawable-mdpi/validation_arrow.png \ res/drawable-mdpi/validation_arrow_inverted.png \ res/drawable-mdpi/validation_bg.9.png \ res/drawable-mdpi/bookmarkdefaults_favicon_support.png \ res/drawable-mdpi/bookmarkdefaults_favicon_addons.png \ res/drawable-mdpi/handle_end.png \ res/drawable-mdpi/handle_middle.png \ res/drawable-mdpi/handle_start.png \ res/drawable-mdpi/scrollbar.png \ res/drawable-mdpi/shadow.png \ res/drawable-mdpi/start.png \ res/drawable-mdpi/marketplace.png \ + res/drawable-mdpi/history_tabs_indicator_selected.9.png \ res/drawable-mdpi/warning.png \ res/drawable-mdpi/warning_doorhanger.png \ $(NULL) RES_DRAWABLE_LDPI = \ $(SYNC_RES_DRAWABLE_LDPI) \ $(NULL) RES_DRAWABLE_HDPI = \ $(SYNC_RES_DRAWABLE_HDPI) \ res/drawable-hdpi/blank.png \ res/drawable-hdpi/favicon.png \ res/drawable-hdpi/folder.png \ res/drawable-hdpi/home_bg.png \ res/drawable-hdpi/home_star.png \ - res/drawable-hdpi/abouthome_icon.png \ - res/drawable-hdpi/abouthome_logo_dark.png \ - res/drawable-hdpi/abouthome_logo_light.png \ - res/drawable-hdpi/abouthome_promo_box_bg.9.png \ - res/drawable-hdpi/abouthome_promo_box_pressed_bg.9.png \ - res/drawable-hdpi/abouthome_promo_logo_apps.png \ - res/drawable-hdpi/abouthome_promo_logo_sync.png \ res/drawable-hdpi/abouthome_thumbnail.png \ - res/drawable-hdpi/abouthome_thumbnail_bg.png \ - res/drawable-hdpi/abouthome_thumbnail_add.png \ - res/drawable-hdpi/address_bar_bg_shadow.png \ res/drawable-hdpi/alert_addon.png \ res/drawable-hdpi/alert_app.png \ res/drawable-hdpi/alert_download.png \ + res/drawable-hdpi/bookmark_folder_closed.png \ + res/drawable-hdpi/bookmark_folder_opened.png \ res/drawable-hdpi/alert_camera.png \ res/drawable-hdpi/alert_mic.png \ res/drawable-hdpi/alert_mic_camera.png \ res/drawable-hdpi/arrow_popup_bg.9.png \ - res/drawable-hdpi/awesomebar_tab_center.9.png \ - res/drawable-hdpi/awesomebar_tab_left.9.png \ - res/drawable-hdpi/awesomebar_tab_right.9.png \ - res/drawable-hdpi/awesomebar_sep_left.9.png \ - res/drawable-hdpi/awesomebar_sep_right.9.png \ - res/drawable-hdpi/ic_addons_empty.png \ - res/drawable-hdpi/ic_awesomebar_go.png \ - res/drawable-hdpi/ic_awesomebar_reader.png \ - res/drawable-hdpi/ic_awesomebar_search.png \ - res/drawable-hdpi/ic_awesomebar_star.png \ - res/drawable-hdpi/ic_awesomebar_tab.png \ + res/drawable-hdpi/home_tab_menu_strip.9.png \ res/drawable-hdpi/ic_menu_addons_filler.png \ res/drawable-hdpi/ic_menu_bookmark_add.png \ res/drawable-hdpi/ic_menu_bookmark_remove.png \ res/drawable-hdpi/ic_menu_character_encoding.png \ res/drawable-hdpi/ic_menu_close_all_tabs.png \ res/drawable-hdpi/ic_menu_forward.png \ res/drawable-hdpi/ic_menu_guest.png \ res/drawable-hdpi/ic_menu_new_private_tab.png \ res/drawable-hdpi/ic_menu_new_tab.png \ res/drawable-hdpi/ic_menu_reload.png \ res/drawable-hdpi/ic_status_logo.png \ + res/drawable-hdpi/ic_url_bar_go.png \ + res/drawable-hdpi/ic_url_bar_reader.png \ + res/drawable-hdpi/ic_url_bar_search.png \ + res/drawable-hdpi/ic_url_bar_star.png \ + res/drawable-hdpi/ic_url_bar_tab.png \ + res/drawable-hdpi/icon_last_tabs.png \ + res/drawable-hdpi/icon_last_tabs_empty.png \ + res/drawable-hdpi/icon_most_recent.png \ + res/drawable-hdpi/icon_most_recent_empty.png \ + res/drawable-hdpi/icon_most_visited.png \ + res/drawable-hdpi/icon_most_visited_empty.png \ res/drawable-hdpi/icon_pageaction.png \ + res/drawable-hdpi/icon_reading_list_empty.png \ res/drawable-hdpi/tab_indicator_divider.9.png \ res/drawable-hdpi/tab_indicator_selected.9.png \ res/drawable-hdpi/tab_indicator_selected_focused.9.png \ res/drawable-hdpi/spinner_default.9.png \ res/drawable-hdpi/spinner_focused.9.png \ res/drawable-hdpi/spinner_pressed.9.png \ res/drawable-hdpi/tab_new.png \ res/drawable-hdpi/tab_new_pb.png \ res/drawable-hdpi/tab_close.png \ res/drawable-hdpi/tab_thumbnail_default.png \ res/drawable-hdpi/tab_thumbnail_shadow.png \ res/drawable-hdpi/tabs_count.png \ res/drawable-hdpi/tabs_count_foreground.png \ + res/drawable-hdpi/url_bar_bg_shadow.png \ + res/drawable-hdpi/url_bar_entry_default.9.png \ + res/drawable-hdpi/url_bar_entry_default_pb.9.png \ + res/drawable-hdpi/url_bar_entry_pressed.9.png \ + res/drawable-hdpi/url_bar_entry_pressed_pb.9.png \ res/drawable-hdpi/tip_addsearch.png \ - res/drawable-hdpi/address_bar_url_default.9.png \ - res/drawable-hdpi/address_bar_url_default_pb.9.png \ - res/drawable-hdpi/address_bar_url_pressed.9.png \ - res/drawable-hdpi/address_bar_url_pressed_pb.9.png \ res/drawable-hdpi/find_close.png \ res/drawable-hdpi/find_next.png \ res/drawable-hdpi/find_prev.png \ res/drawable-hdpi/larry.png \ res/drawable-hdpi/lock_identified.png \ res/drawable-hdpi/lock_verified.png \ res/drawable-hdpi/menu.png \ res/drawable-hdpi/menu_pb.png \ @@ -797,95 +818,95 @@ RES_DRAWABLE_HDPI = \ res/drawable-hdpi/menu_item_check.png \ res/drawable-hdpi/menu_item_more.png \ res/drawable-hdpi/menu_item_uncheck.png \ res/drawable-hdpi/shield.png \ res/drawable-hdpi/shield_doorhanger.png \ res/drawable-hdpi/tabs_normal.png \ res/drawable-hdpi/tabs_private.png \ res/drawable-hdpi/tabs_synced.png \ + res/drawable-hdpi/top_bookmark_add.png \ res/drawable-hdpi/urlbar_stop.png \ res/drawable-hdpi/reader.png \ + res/drawable-hdpi/reader_cropped.png \ res/drawable-hdpi/reader_active.png \ res/drawable-hdpi/reading_list.png \ res/drawable-hdpi/validation_arrow.png \ res/drawable-hdpi/validation_arrow_inverted.png \ res/drawable-hdpi/validation_bg.9.png \ res/drawable-hdpi/handle_end.png \ res/drawable-hdpi/handle_middle.png \ res/drawable-hdpi/handle_start.png \ + res/drawable-hdpi/history_tabs_indicator_selected.9.png \ res/drawable-hdpi/warning.png \ res/drawable-hdpi/warning_doorhanger.png \ $(NULL) RES_DRAWABLE_XHDPI = \ res/drawable-xhdpi/blank.png \ res/drawable-xhdpi/favicon.png \ res/drawable-xhdpi/folder.png \ - res/drawable-xhdpi/abouthome_icon.png \ - res/drawable-xhdpi/abouthome_logo_dark.png \ - res/drawable-xhdpi/abouthome_logo_light.png \ - res/drawable-xhdpi/abouthome_promo_box_bg.9.png \ - res/drawable-xhdpi/abouthome_promo_box_pressed_bg.9.png \ - res/drawable-xhdpi/abouthome_promo_logo_apps.png \ - res/drawable-xhdpi/abouthome_promo_logo_sync.png \ res/drawable-xhdpi/abouthome_thumbnail.png \ - res/drawable-xhdpi/abouthome_thumbnail_bg.png \ - res/drawable-xhdpi/abouthome_thumbnail_add.png \ - res/drawable-xhdpi/address_bar_bg_shadow.png \ - res/drawable-xhdpi/address_bar_url_default.9.png \ - res/drawable-xhdpi/address_bar_url_default_pb.9.png \ - res/drawable-xhdpi/address_bar_url_pressed.9.png \ - res/drawable-xhdpi/address_bar_url_pressed_pb.9.png \ + res/drawable-xhdpi/url_bar_bg_shadow.png \ + res/drawable-xhdpi/url_bar_entry_default.9.png \ + res/drawable-xhdpi/url_bar_entry_default_pb.9.png \ + res/drawable-xhdpi/url_bar_entry_pressed.9.png \ + res/drawable-xhdpi/url_bar_entry_pressed_pb.9.png \ res/drawable-xhdpi/alert_addon.png \ res/drawable-xhdpi/alert_app.png \ res/drawable-xhdpi/alert_download.png \ + res/drawable-xhdpi/bookmark_folder_closed.png \ + res/drawable-xhdpi/bookmark_folder_opened.png \ res/drawable-xhdpi/alert_camera.png \ res/drawable-xhdpi/alert_mic.png \ res/drawable-xhdpi/alert_mic_camera.png \ res/drawable-xhdpi/arrow_popup_bg.9.png \ - res/drawable-xhdpi/awesomebar_tab_center.9.png \ - res/drawable-xhdpi/awesomebar_tab_left.9.png \ - res/drawable-xhdpi/awesomebar_tab_right.9.png \ - res/drawable-xhdpi/awesomebar_sep_left.9.png \ - res/drawable-xhdpi/awesomebar_sep_right.9.png \ - res/drawable-xhdpi/ic_addons_empty.png \ - res/drawable-xhdpi/ic_awesomebar_go.png \ - res/drawable-xhdpi/ic_awesomebar_reader.png \ - res/drawable-xhdpi/ic_awesomebar_search.png \ - res/drawable-xhdpi/ic_awesomebar_star.png \ - res/drawable-xhdpi/ic_awesomebar_tab.png \ + res/drawable-xhdpi/home_tab_menu_strip.9.png \ res/drawable-xhdpi/ic_menu_addons_filler.png \ res/drawable-xhdpi/ic_menu_bookmark_add.png \ res/drawable-xhdpi/ic_menu_bookmark_remove.png \ res/drawable-xhdpi/ic_menu_close_all_tabs.png \ res/drawable-xhdpi/ic_menu_character_encoding.png \ res/drawable-xhdpi/ic_menu_forward.png \ res/drawable-xhdpi/ic_menu_guest.png \ res/drawable-xhdpi/ic_menu_new_private_tab.png \ res/drawable-xhdpi/ic_menu_new_tab.png \ res/drawable-xhdpi/ic_menu_reload.png \ res/drawable-xhdpi/ic_status_logo.png \ + res/drawable-xhdpi/ic_url_bar_go.png \ + res/drawable-xhdpi/ic_url_bar_reader.png \ + res/drawable-xhdpi/ic_url_bar_search.png \ + res/drawable-xhdpi/ic_url_bar_star.png \ + res/drawable-xhdpi/ic_url_bar_tab.png \ + res/drawable-xhdpi/icon_last_tabs.png \ + res/drawable-xhdpi/icon_last_tabs_empty.png \ + res/drawable-xhdpi/icon_most_recent.png \ + res/drawable-xhdpi/icon_most_recent_empty.png \ + res/drawable-xhdpi/icon_most_visited.png \ + res/drawable-xhdpi/icon_most_visited_empty.png \ res/drawable-xhdpi/icon_pageaction.png \ + res/drawable-xhdpi/icon_reading_list_empty.png \ res/drawable-xhdpi/spinner_default.9.png \ res/drawable-xhdpi/spinner_focused.9.png \ res/drawable-xhdpi/spinner_pressed.9.png \ res/drawable-xhdpi/tab_new.png \ res/drawable-xhdpi/tab_new_pb.png \ res/drawable-xhdpi/tab_close.png \ res/drawable-xhdpi/tab_thumbnail_default.png \ res/drawable-xhdpi/tab_thumbnail_shadow.png \ res/drawable-xhdpi/tabs_count.png \ res/drawable-xhdpi/tabs_count_foreground.png \ res/drawable-xhdpi/tip_addsearch.png \ res/drawable-xhdpi/find_close.png \ res/drawable-xhdpi/find_next.png \ res/drawable-xhdpi/find_prev.png \ + res/drawable-xhdpi/top_bookmark_add.png \ res/drawable-xhdpi/urlbar_stop.png \ res/drawable-xhdpi/reader.png \ + res/drawable-xhdpi/reader_cropped.png \ res/drawable-xhdpi/reader_active.png \ res/drawable-xhdpi/reading_list.png \ res/drawable-xhdpi/larry.png \ res/drawable-xhdpi/lock_identified.png \ res/drawable-xhdpi/lock_verified.png \ res/drawable-xhdpi/menu.png \ res/drawable-xhdpi/menu_pb.png \ res/drawable-xhdpi/menu_panel_bg.9.png \ @@ -904,16 +925,17 @@ RES_DRAWABLE_XHDPI = \ res/drawable-xhdpi/tabs_private.png \ res/drawable-xhdpi/tabs_synced.png \ res/drawable-xhdpi/validation_arrow.png \ res/drawable-xhdpi/validation_arrow_inverted.png \ res/drawable-xhdpi/validation_bg.9.png \ res/drawable-xhdpi/handle_end.png \ res/drawable-xhdpi/handle_middle.png \ res/drawable-xhdpi/handle_start.png \ + res/drawable-xhdpi/history_tabs_indicator_selected.9.png \ res/drawable-xhdpi/warning.png \ res/drawable-xhdpi/warning_doorhanger.png \ $(NULL) RES_DRAWABLE_MDPI_V11 = \ res/drawable-mdpi-v11/alert_addon.png \ res/drawable-mdpi-v11/alert_app.png \ res/drawable-mdpi-v11/alert_download.png \ @@ -995,16 +1017,20 @@ RES_DRAWABLE_XHDPI_V11 = \ res/drawable-xhdpi-v11/ic_menu_save_as_pdf.png \ res/drawable-xhdpi-v11/ic_menu_settings.png \ res/drawable-xhdpi-v11/ic_menu_share.png \ res/drawable-xhdpi-v11/ic_menu_tools.png \ res/drawable-xhdpi-v11/ic_menu_quit.png \ res/drawable-xhdpi-v11/ic_status_logo.png \ $(NULL) +RES_DRAWABLE_LARGE_LAND_V11 = \ + res/drawable-large-land-v11/home_history_tabs_indicator.xml \ + $(NULL) + RES_DRAWABLE_LARGE_MDPI_V11 = \ res/drawable-large-mdpi-v11/arrow_popup_bg.9.png \ res/drawable-large-mdpi-v11/ic_menu_reload.png \ res/drawable-large-mdpi-v11/ic_menu_forward.png \ res/drawable-large-mdpi-v11/menu.png \ $(NULL) RES_DRAWABLE_LARGE_HDPI_V11 = \ @@ -1016,100 +1042,85 @@ RES_DRAWABLE_LARGE_HDPI_V11 = \ RES_DRAWABLE_LARGE_XHDPI_V11 = \ res/drawable-large-xhdpi-v11/arrow_popup_bg.9.png \ res/drawable-large-xhdpi-v11/ic_menu_reload.png \ res/drawable-large-xhdpi-v11/ic_menu_forward.png \ res/drawable-large-xhdpi-v11/menu.png \ $(NULL) +RES_DRAWABLE_XLARGE_V11 = \ + res/drawable-xlarge-v11/home_history_tabs_indicator.xml \ + $(NULL) + RES_DRAWABLE_XLARGE_MDPI_V11 = \ - res/drawable-xlarge-mdpi-v11/awesomebar_tab_center.9.png \ - res/drawable-xlarge-mdpi-v11/awesomebar_tab_left.9.png \ - res/drawable-xlarge-mdpi-v11/awesomebar_tab_right.9.png \ - res/drawable-xlarge-mdpi-v11/awesomebar_sep_left.9.png \ - res/drawable-xlarge-mdpi-v11/awesomebar_sep_right.9.png \ res/drawable-xlarge-mdpi-v11/ic_menu_bookmark_add.png \ res/drawable-xlarge-mdpi-v11/ic_menu_bookmark_remove.png \ $(NULL) RES_DRAWABLE_XLARGE_HDPI_V11 = \ - res/drawable-xlarge-hdpi-v11/awesomebar_tab_center.9.png \ - res/drawable-xlarge-hdpi-v11/awesomebar_tab_left.9.png \ - res/drawable-xlarge-hdpi-v11/awesomebar_tab_right.9.png \ - res/drawable-xlarge-hdpi-v11/awesomebar_sep_left.9.png \ - res/drawable-xlarge-hdpi-v11/awesomebar_sep_right.9.png \ res/drawable-xlarge-hdpi-v11/ic_menu_bookmark_add.png \ res/drawable-xlarge-hdpi-v11/ic_menu_bookmark_remove.png \ $(NULL) RES_DRAWABLE_XLARGE_XHDPI_V11 = \ - res/drawable-xlarge-xhdpi-v11/awesomebar_tab_center.9.png \ - res/drawable-xlarge-xhdpi-v11/awesomebar_tab_left.9.png \ - res/drawable-xlarge-xhdpi-v11/awesomebar_tab_right.9.png \ - res/drawable-xlarge-xhdpi-v11/awesomebar_sep_left.9.png \ - res/drawable-xlarge-xhdpi-v11/awesomebar_sep_right.9.png \ res/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_add.png \ res/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_remove.png \ $(NULL) RES_COLOR = \ - res/color/abouthome_section_more_text.xml \ - res/color/abouthome_section_subtitle.xml \ - res/color/abouthome_section_title.xml \ - res/color/awesome_bar_title.xml \ - res/color/awesome_bar_title_hint.xml \ res/color/primary_text.xml \ res/color/primary_text_inverse.xml \ res/color/secondary_text.xml \ res/color/secondary_text_inverse.xml \ res/color/select_item_multichoice.xml \ res/color/tertiary_text.xml \ res/color/tertiary_text_inverse.xml \ + res/color/top_bookmark_item_title.xml \ + res/color/url_bar_title.xml \ + res/color/url_bar_title_hint.xml \ $(NULL) RES_MENU = \ - res/menu/abouthome_topsites_contextmenu.xml \ - res/menu/awesomebar_contextmenu.xml \ res/menu/browser_app_menu.xml \ res/menu/gecko_app_menu.xml \ + res/menu/home_contextmenu.xml \ res/menu/titlebar_contextmenu.xml \ + res/menu/top_bookmarks_contextmenu.xml \ res/menu-large-v11/browser_app_menu.xml \ res/menu-v11/browser_app_menu.xml \ res/menu-xlarge-v11/browser_app_menu.xml \ $(NULL) JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar ifdef MOZ_CRASHREPORTER FENNEC_JAVA_FILES += CrashReporter.java RES_DRAWABLE_MDPI += res/drawable-mdpi/crash_reporter.png RES_LAYOUT += res/layout/crash_reporter.xml endif RES_DRAWABLE += \ - $(SYNC_RES_DRAWABLE) \ - res/drawable/abouthome_logo.xml \ - res/drawable/abouthome_promo_box.xml \ + $(SYNC_RES_DRAWABLE) \ res/drawable/action_bar_button.xml \ res/drawable/action_bar_button_inverse.xml \ - res/drawable/address_bar_bg.xml \ - res/drawable/address_bar_bg_shadow_repeat.xml \ - res/drawable/address_bar_nav_button.xml \ - res/drawable/address_bar_right_edge.xml \ - res/drawable/address_bar_url.xml \ - res/drawable/awesomebar_listview_divider.xml \ - res/drawable/awesomebar_header_row.xml \ - res/drawable/awesomebar_tab_indicator.xml \ - res/drawable/awesomebar_tab_selected.xml \ - res/drawable/awesomebar_tab_unselected.xml \ + res/drawable/bookmark_thumbnail_bg.xml \ + res/drawable/url_bar_bg.xml \ + res/drawable/url_bar_bg_shadow_repeat.xml \ + res/drawable/url_bar_entry.xml \ + res/drawable/url_bar_nav_button.xml \ + res/drawable/url_bar_right_edge.xml \ + res/drawable/bookmark_folder.xml \ + res/drawable/divider_horizontal.xml \ res/drawable/divider_vertical.xml \ res/drawable/favicon_bg.xml \ res/drawable/handle_end_level.xml \ res/drawable/handle_start_level.xml \ + res/drawable/home_history_tabs_indicator.xml \ + res/drawable/home_page_title_background.xml \ res/drawable/ic_menu_back.xml \ res/drawable/ic_menu_desktop_mode_off.xml \ res/drawable/ic_menu_desktop_mode_on.xml \ res/drawable/ic_menu_quit.xml \ res/drawable/menu_item_state.xml \ res/drawable/menu_level.xml \ res/drawable/remote_tabs_child_divider.xml \ res/drawable/shaped_button.xml \ @@ -1126,70 +1137,77 @@ RES_DRAWABLE += \ $(NULL) RESOURCES = \ $(RES_ANIM) \ $(RES_COLOR) \ $(RES_DRAWABLE) \ $(RES_DRAWABLE_HDPI) \ $(RES_DRAWABLE_HDPI_V11) \ + $(RES_DRAWABLE_LARGE_LAND_V11) \ $(RES_DRAWABLE_LARGE_HDPI_V11) \ $(RES_DRAWABLE_LARGE_MDPI_V11) \ $(RES_DRAWABLE_LARGE_XHDPI_V11) \ $(RES_DRAWABLE_LDPI) \ $(RES_DRAWABLE_MDPI) \ $(RES_DRAWABLE_MDPI_V11) \ $(RES_DRAWABLE_XHDPI) \ $(RES_DRAWABLE_XHDPI_V11) \ + $(RES_DRAWABLE_XLARGE_V11) \ $(RES_DRAWABLE_XLARGE_HDPI_V11) \ $(RES_DRAWABLE_XLARGE_MDPI_V11) \ $(RES_DRAWABLE_XLARGE_XHDPI_V11) \ $(RES_LAYOUT) \ $(RES_LAYOUT_LARGE_LAND_V11) \ $(RES_LAYOUT_LARGE_V11) \ $(RES_LAYOUT_XLARGE_LAND_V11) \ $(RES_LAYOUT_XLARGE_V11) \ $(RES_MENU) \ $(RES_VALUES) \ $(RES_VALUES_LAND) \ $(RES_VALUES_LAND_V14) \ $(RES_VALUES_LARGE_LAND_V11) \ $(RES_VALUES_LARGE_V11) \ $(RES_VALUES_V11) \ $(RES_VALUES_V14) \ + $(RES_VALUES_V16) \ + $(RES_VALUES_XLARGE_LAND_V11) \ $(RES_VALUES_XLARGE_V11) \ $(RES_XML) \ $(RES_XML_V11) \ $(NULL) RES_DIRS= \ res/layout \ res/layout-large-v11 \ res/layout-large-land-v11 \ res/layout-xlarge-v11 \ - res/layout-xlarge-land-v11 \ res/values \ res/values-v11 \ res/values-large-v11 \ + res/values-xlarge-land-v11 \ res/values-xlarge-v11 \ - res/values-land-v14 \ + res/values-v14 \ + res/values-v16 \ res/xml \ res/xml-v11 \ res/anim \ res/drawable-ldpi \ res/drawable-mdpi \ res/drawable-hdpi \ res/drawable-xhdpi \ res/drawable \ res/drawable-mdpi-v11 \ res/drawable-hdpi-v11 \ res/drawable-xhdpi-v11 \ + res/drawable-large-land-v11 \ res/drawable-large-mdpi-v11 \ res/drawable-large-hdpi-v11 \ res/drawable-large-xhdpi-v11 \ + res/drawable-xlarge-v11 \ res/drawable-xlarge-mdpi-v11 \ res/drawable-xlarge-hdpi-v11 \ res/drawable-xlarge-xhdpi-v11 \ res/color \ res/menu \ res/menu-v11 \ res/menu-large-v11 \ res/menu-xlarge-v11 \
--- a/mobile/android/base/PageActionLayout.java +++ b/mobile/android/base/PageActionLayout.java @@ -141,17 +141,17 @@ public class PageActionLayout extends Li mPageActionList.remove(i); refreshPageActionIcons(); return; } } } private ImageButton createImageButton() { - ImageButton imageButton = new ImageButton(mContext, null, R.style.AddressBar_ImageButton_Icon); + ImageButton imageButton = new ImageButton(mContext, null, R.style.UrlBar_ImageButton_Icon); imageButton.setLayoutParams(new LayoutParams(mContext.getResources().getDimensionPixelSize(R.dimen.page_action_button_width), LayoutParams.MATCH_PARENT)); imageButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE); imageButton.setOnClickListener(this); imageButton.setOnLongClickListener(this); return imageButton; } @Override
deleted file mode 100644 --- a/mobile/android/base/SearchEngineRow.java +++ /dev/null @@ -1,231 +0,0 @@ -/* -*- 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.AwesomeBarTabs.OnUrlOpenListener; -import org.mozilla.gecko.util.GamepadUtils; -import org.mozilla.gecko.util.StringUtils; -import org.mozilla.gecko.widget.FaviconView; - -import android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; -import android.view.animation.AlphaAnimation; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -class SearchEngineRow extends AnimatedHeightLayout { - // Duration for fade-in animation - private static final int ANIMATION_DURATION = 250; - - // 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; - - // Selected suggestion view - private int mSelectedView = 0; - - // Event listeners for suggestion views - private final OnClickListener mClickListener; - private final OnLongClickListener mLongClickListener; - - // On URL open listener - private OnUrlOpenListener mUrlOpenListener; - - 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 (mUrlOpenListener != null) { - if (v != mUserEnteredView && !StringUtils.isSearchQuery(suggestion, false)) { - mUrlOpenListener.onUrlOpen(suggestion, null); - } else { - mUrlOpenListener.onSearch(mSearchEngine, suggestion); - } - } - } - }; - - mLongClickListener = new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (mUrlOpenListener != null) { - final String suggestion = getSuggestionTextFromView(v); - mUrlOpenListener.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); - } - - private String getSuggestionTextFromView(View v) { - final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text); - return suggestionText.getText().toString(); - } - - private void setSuggestionOnView(View v, String suggestion) { - final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text); - suggestionText.setText(suggestion); - } - - public void setSearchTerm(String searchTerm) { - mUserEnteredTextView.setText(searchTerm); - } - - public void setOnUrlOpenListener(OnUrlOpenListener listener) { - mUrlOpenListener = listener; - } - - public void updateFromSearchEngine(SearchEngine searchEngine, boolean doAnimation) { - // Update search engine reference - mSearchEngine = searchEngine; - - // Set the search engine icon (e.g., Google) for the row - mIconView.updateImage(mSearchEngine.icon, mSearchEngine.name); - - // Add additional suggestions given by this engine - final int recycledSuggestionCount = mSuggestionView.getChildCount(); - final int suggestionCount = mSearchEngine.suggestions.size(); - - for (int i = 0; i < suggestionCount; i++) { - final View suggestionItem; - - // Reuse suggestion views from recycled view, if possible - if (i + 1 < recycledSuggestionCount) { - suggestionItem = mSuggestionView.getChildAt(i + 1); - suggestionItem.setVisibility(View.VISIBLE); - } else { - suggestionItem = mInflater.inflate(R.layout.suggestion_item, null); - - suggestionItem.setOnClickListener(mClickListener); - suggestionItem.setOnLongClickListener(mLongClickListener); - - final ImageView magnifier = - (ImageView) suggestionItem.findViewById(R.id.suggestion_magnifier); - magnifier.setVisibility(View.GONE); - - mSuggestionView.addView(suggestionItem); - } - - final String suggestion = mSearchEngine.suggestions.get(i); - setSuggestionOnView(suggestionItem, suggestion); - - if (doAnimation) { - AlphaAnimation anim = new AlphaAnimation(0, 1); - anim.setDuration(ANIMATION_DURATION); - anim.setStartOffset(i * ANIMATION_DURATION); - suggestionItem.startAnimation(anim); - } - } - - // Hide extra suggestions that have been recycled - for (int i = suggestionCount + 1; i < recycledSuggestionCount; i++) { - mSuggestionView.getChildAt(i).setVisibility(View.GONE); - } - - // Make sure mSelectedView is still valid - if (mSelectedView >= mSuggestionView.getChildCount()) { - mSelectedView = mSuggestionView.getChildCount() - 1; - } - } - - @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(); - } -}
--- a/mobile/android/base/Tab.java +++ b/mobile/android/base/Tab.java @@ -2,16 +2,17 @@ * 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.gfx.Layer; +import org.mozilla.gecko.home.HomePager; import org.mozilla.gecko.util.ThreadUtils; import org.json.JSONException; import org.json.JSONObject; import android.content.ContentResolver; import android.content.Context; import android.graphics.Bitmap; @@ -44,16 +45,17 @@ public class Tab { private int mFaviconSize; private boolean mFeedsEnabled; private JSONObject mIdentityData; private boolean mReaderEnabled; private BitmapDrawable mThumbnail; private int mHistoryIndex; private int mHistorySize; private int mParentId; + private HomePager.Page mAboutHomePage; private boolean mExternal; private boolean mBookmark; private boolean mReadingListItem; private long mFaviconLoadId; private String mContentType; private boolean mHasTouchListeners; private ZoomConstraints mZoomConstraints; private boolean mIsRTL; @@ -68,32 +70,35 @@ public class Tab { private ErrorType mErrorType = ErrorType.NONE; private static final int MAX_HISTORY_LIST_SIZE = 50; public static final int STATE_DELAYED = 0; public static final int STATE_LOADING = 1; public static final int STATE_SUCCESS = 2; public static final int STATE_ERROR = 3; + private static final int DEFAULT_BACKGROUND_COLOR = Color.WHITE; + public enum ErrorType { CERT_ERROR, // Pages with certificate problems BLOCKED, // Pages blocked for phishing or malware warnings - NET_ERROR, // All other types of error + NET_ERROR, // All other types of error NONE // Non error pages } public Tab(Context context, int id, String url, boolean external, int parentId, String title) { mAppContext = context.getApplicationContext(); mId = id; mLastUsed = 0; mUrl = url; mBaseDomain = ""; mUserSearch = ""; mExternal = external; mParentId = parentId; + mAboutHomePage = HomePager.Page.BOOKMARKS; mTitle = title == null ? "" : title; mFavicon = null; mFaviconUrl = null; mFaviconSize = 0; mFeedsEnabled = false; mIdentityData = null; mReaderEnabled = false; mEnteringReaderMode = false; @@ -107,17 +112,17 @@ public class Tab { mZoomConstraints = new ZoomConstraints(false); mPluginViews = new ArrayList<View>(); mPluginLayers = new HashMap<Object, Layer>(); mState = shouldShowProgress(url) ? STATE_SUCCESS : STATE_LOADING; // At startup, the background is set to a color specified by LayerView // when the LayerView is created. Shortly after, this background color // will be used before the tab's content is shown. - mBackgroundColor = getBackgroundColorForUrl(url); + mBackgroundColor = DEFAULT_BACKGROUND_COLOR; } private ContentResolver getContentResolver() { return mAppContext.getContentResolver(); } public void onDestroy() { Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.CLOSED); @@ -134,16 +139,25 @@ public class Tab { public synchronized long getLastUsed() { return mLastUsed; } public int getParentId() { return mParentId; } + public HomePager.Page getAboutHomePage() { + return mAboutHomePage; + } + + private void setAboutHomePage(HomePager.Page page) { + mAboutHomePage = page; + } + + // may be null if user-entered query hasn't yet been resolved to a URI public synchronized String getURL() { return mUrl; } // mUserSearch should never be null, but it may be an empty string public synchronized String getUserSearch() { return mUserSearch; @@ -602,33 +616,31 @@ public class Tab { setContentType(message.getString("contentType")); clearFavicon(); setFeedsEnabled(false); updateTitle(null); updateIdentityData(null); setReaderEnabled(false); setZoomConstraints(new ZoomConstraints(true)); setHasTouchListeners(false); - setBackgroundColor(getBackgroundColorForUrl(uri)); + setBackgroundColor(DEFAULT_BACKGROUND_COLOR); setErrorType(ErrorType.NONE); + final String homePage = message.getString("aboutHomePage"); + if (!TextUtils.isEmpty(homePage)) { + setAboutHomePage(HomePager.Page.valueOf(homePage)); + } + Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, uri); } private boolean shouldShowProgress(String url) { return "about:home".equals(url) || ReaderModeUtils.isAboutReader(url); } - private int getBackgroundColorForUrl(String url) { - if ("about:home".equals(url)) { - return mAppContext.getResources().getColor(R.color.background_normal); - } - return Color.WHITE; - } - void handleDocumentStart(boolean showProgress, String url) { setState(shouldShowProgress(url) ? STATE_SUCCESS : STATE_LOADING); updateIdentityData(null); setReaderEnabled(false); } void handleDocumentStop(boolean success) { setState(success ? STATE_SUCCESS : STATE_ERROR);
--- a/mobile/android/base/Tabs.java +++ b/mobile/android/base/Tabs.java @@ -1,32 +1,35 @@ /* -*- 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.home.HomePager; +import org.mozilla.gecko.ReaderModeUtils; import org.mozilla.gecko.sync.setup.SyncAccounts; import org.mozilla.gecko.util.GeckoEventListener; import org.mozilla.gecko.util.ThreadUtils; import org.json.JSONObject; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.OnAccountsUpdateListener; import android.app.Activity; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.graphics.Color; import android.net.Uri; import android.os.Handler; +import android.text.TextUtils; import android.util.Log; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -52,16 +55,17 @@ public class Tabs implements GeckoEventL public static final int LOADURL_NEW_TAB = 1 << 0; public static final int LOADURL_USER_ENTERED = 1 << 1; public static final int LOADURL_PRIVATE = 1 << 2; public static final int LOADURL_PINNED = 1 << 3; public static final int LOADURL_DELAY_LOAD = 1 << 4; public static final int LOADURL_DESKTOP = 1 << 5; public static final int LOADURL_BACKGROUND = 1 << 6; public static final int LOADURL_EXTERNAL = 1 << 7; + public static final int LOADURL_READING_LIST = 1 << 8; private static final long PERSIST_TABS_AFTER_MILLISECONDS = 1000 * 5; private static AtomicInteger sTabId = new AtomicInteger(0); private volatile boolean mInitialTabsAdded; private Context mAppContext; private ContentObserver mContentObserver; @@ -577,16 +581,32 @@ public class Tabs implements GeckoEventL backgroundHandler.postDelayed(mPersistTabsRunnable, PERSIST_TABS_AFTER_MILLISECONDS); } private void registerEventListener(String event) { GeckoAppShell.getEventDispatcher().registerEventListener(event, this); } /** + * Looks for an open tab with the given URL. + * + * @return id of an open tab with the given URL; -1 if the tab doesn't exist. + */ + public int getTabIdForUrl(String url) { + for (Tab tab : mOrder) { + if (TextUtils.equals(tab.getURL(), url) || + TextUtils.equals(ReaderModeUtils.getUrlFromAboutReader(tab.getURL()), url)) { + return tab.getId(); + } + } + + return -1; + } + + /** * Loads a tab with the given URL in the currently selected tab. * * @param url URL of page to load, or search term used if searchEngine is given */ public void loadUrl(String url) { loadUrl(url, LOADURL_NONE); } @@ -632,16 +652,17 @@ public class Tabs implements GeckoEventL args.put("parentId", parentId); args.put("userEntered", userEntered); args.put("newTab", (flags & LOADURL_NEW_TAB) != 0); args.put("isPrivate", isPrivate); args.put("pinned", (flags & LOADURL_PINNED) != 0); args.put("delayLoad", delayLoad); args.put("desktopMode", desktopMode); args.put("selected", !background); + args.put("aboutHomePage", (flags & LOADURL_READING_LIST) != 0 ? HomePager.Page.READING_LIST : ""); if ((flags & LOADURL_NEW_TAB) != 0) { int tabId = getNextTabId(); args.put("tabID", tabId); // The URL is updated for the tab once Gecko responds with the // Tab:Added message. We can preliminarily set the tab's URL as // long as it's a valid URI.
--- a/mobile/android/base/TabsPanel.java +++ b/mobile/android/base/TabsPanel.java @@ -100,29 +100,21 @@ public class TabsPanel extends LinearLay mAddTab = (ImageButton) findViewById(R.id.add_tab); mAddTab.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { TabsPanel.this.addTab(); } }); - ImageButton button; - Resources resources = getContext().getResources(); - mTabWidget = (IconTabWidget) findViewById(R.id.tab_widget); - button = mTabWidget.addTab(R.drawable.tabs_normal); - button.setContentDescription(resources.getString(R.string.tabs_normal)); - - button = mTabWidget.addTab(R.drawable.tabs_private); - button.setContentDescription(resources.getString(R.string.tabs_private)); - - button = mTabWidget.addTab(R.drawable.tabs_synced); - button.setContentDescription(resources.getString(R.string.tabs_synced)); + mTabWidget.addTab(R.drawable.tabs_normal, R.string.tabs_normal); + mTabWidget.addTab(R.drawable.tabs_private, R.string.tabs_private); + mTabWidget.addTab(R.drawable.tabs_synced, R.string.tabs_synced); mTabWidget.setTabSelectionListener(this); } public void addTab() { if (mCurrentPanel == Panel.NORMAL_TABS) mActivity.addTab(); else
--- a/mobile/android/base/TabsTray.java +++ b/mobile/android/base/TabsTray.java @@ -310,17 +310,17 @@ public class TabsTray extends TwoWayView if (isVertical()) animator.attach(view, Property.TRANSLATION_X, pos); else animator.attach(view, Property.TRANSLATION_Y, pos); mCloseAnimationCount++; mPendingClosedTabs.add(view); - animator.setPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { + animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { @Override public void onPropertyAnimationStart() { } @Override public void onPropertyAnimationEnd() { mCloseAnimationCount--; if (mCloseAnimationCount > 0) return; @@ -348,17 +348,17 @@ public class TabsTray extends TwoWayView animator.attach(view, Property.WIDTH, 1); TabRow tab = (TabRow)view.getTag(); final int tabId = tab.id; // Caching this assumes that all rows are the same height if (mOriginalSize == 0) mOriginalSize = (isVertical ? view.getHeight() : view.getWidth()); - animator.setPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { + animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { @Override public void onPropertyAnimationStart() { } @Override public void onPropertyAnimationEnd() { Tabs tabs = Tabs.getInstance(); Tab tab = tabs.getTab(tabId); tabs.closeTab(tab); } @@ -372,17 +372,17 @@ public class TabsTray extends TwoWayView animator.attach(view, Property.ALPHA, 1); if (isVertical()) animator.attach(view, Property.TRANSLATION_X, 0); else animator.attach(view, Property.TRANSLATION_Y, 0); - animator.setPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { + animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { @Override public void onPropertyAnimationStart() { } @Override public void onPropertyAnimationEnd() { TabRow tab = (TabRow) view.getTag(); tab.close.setVisibility(View.VISIBLE); } });
--- a/mobile/android/base/ThumbnailHelper.java +++ b/mobile/android/base/ThumbnailHelper.java @@ -24,17 +24,17 @@ import java.util.concurrent.atomic.Atomi * completion of the current thumbnail the next one is automatically processed. * Changes to the thumbnail width are stashed in mPendingWidth and the change is * applied between thumbnail processing. This allows a single thumbnail buffer to * be used for all thumbnails. */ public final class ThumbnailHelper { private static final String LOGTAG = "GeckoThumbnailHelper"; - public static final float THUMBNAIL_ASPECT_RATIO = 0.714f; // this is a 5:7 ratio (as per UX decision) + public static final float THUMBNAIL_ASPECT_RATIO = 0.571f; // this is a 4:7 ratio (as per UX decision) // static singleton stuff private static ThumbnailHelper sInstance; public static synchronized ThumbnailHelper getInstance() { if (sInstance == null) { sInstance = new ThumbnailHelper();
--- a/mobile/android/base/animation/PropertyAnimator.java +++ b/mobile/android/base/animation/PropertyAnimator.java @@ -6,16 +6,17 @@ package org.mozilla.gecko.animation; import android.support.v4.view.ViewCompat; import android.os.Build; import android.os.Handler; import android.view.Choreographer; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import java.util.ArrayList; import java.util.List; public class PropertyAnimator implements Runnable { @@ -44,31 +45,32 @@ public class PropertyAnimator implements public void onPropertyAnimationEnd(); } private Interpolator mInterpolator; private long mStartTime; private long mDuration; private float mDurationReciprocal; private List<ElementHolder> mElementsList; - private PropertyAnimationListener mListener; + private List<PropertyAnimationListener> mListeners; private FramePoster mFramePoster; private boolean mUseHardwareLayer; public PropertyAnimator(long duration) { this(duration, new DecelerateInterpolator()); } public PropertyAnimator(long duration, Interpolator interpolator) { mDuration = duration; mDurationReciprocal = 1.0f / (float) mDuration; mInterpolator = interpolator; mElementsList = new ArrayList<ElementHolder>(); mFramePoster = FramePoster.create(this); mUseHardwareLayer = true; + mListeners = null; } public void setUseHardwareLayer(boolean useHardwareLayer) { mUseHardwareLayer = useHardwareLayer; } public void attach(View view, Property property, float to) { ElementHolder element = new ElementHolder(); @@ -76,18 +78,22 @@ public class PropertyAnimator implements element.view = view; element.proxy = AnimatorProxy.create(view); element.property = property; element.to = to; mElementsList.add(element); } - public void setPropertyAnimationListener(PropertyAnimationListener listener) { - mListener = listener; + public void addPropertyAnimationListener(PropertyAnimationListener listener) { + if (mListeners == null) { + mListeners = new ArrayList<PropertyAnimationListener>(); + } + + mListeners.add(listener); } public long getDuration() { return mDuration; } public long getRemainingTime() { int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime); @@ -139,20 +145,45 @@ public class PropertyAnimator implements ViewCompat.setHasTransientState(element.view, true); if (shouldEnableHardwareLayer(element)) element.view.setLayerType(View.LAYER_TYPE_HARDWARE, null); else element.view.setDrawingCacheEnabled(true); } - mFramePoster.postFirstAnimationFrame(); + // Get ViewTreeObserver from any of the participant views + // in the animation. + final ViewTreeObserver treeObserver; + if (mElementsList.size() > 0) { + treeObserver = mElementsList.get(0).view.getViewTreeObserver(); + } else { + treeObserver = null; + } - if (mListener != null) - mListener.onPropertyAnimationStart(); + // Try to start animation after any on-going layout round + // in the current view tree. + if (treeObserver != null && treeObserver.isAlive()) { + treeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + treeObserver.removeOnPreDrawListener(this); + mFramePoster.postFirstAnimationFrame(); + return true; + } + }); + } else { + mFramePoster.postFirstAnimationFrame(); + } + + if (mListeners != null) { + for (PropertyAnimationListener listener : mListeners) { + listener.onPropertyAnimationStart(); + } + } } /** * Stop the animation, optionally snapping to the end position. * onPropertyAnimationEnd is only called when snapping to the end position. */ public void stop(boolean snapToEndPosition) { @@ -168,20 +199,25 @@ public class PropertyAnimator implements if (shouldEnableHardwareLayer(element)) element.view.setLayerType(View.LAYER_TYPE_NONE, null); else element.view.setDrawingCacheEnabled(false); } mElementsList.clear(); - if (mListener != null) { - if (snapToEndPosition) - mListener.onPropertyAnimationEnd(); - mListener = null; + if (mListeners != null) { + if (snapToEndPosition) { + for (PropertyAnimationListener listener : mListeners) { + listener.onPropertyAnimationEnd(); + } + } + + mListeners.clear(); + mListeners = null; } } public void stop() { stop(true); } private boolean shouldEnableHardwareLayer(ElementHolder element) {
deleted file mode 100644 --- a/mobile/android/base/awesomebar/AllPagesTab.java +++ /dev/null @@ -1,962 +0,0 @@ -/* -*- 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.AwesomeBar.ContextMenuSubject; -import org.mozilla.gecko.db.BrowserContract.Combined; -import org.mozilla.gecko.db.BrowserDB; -import org.mozilla.gecko.db.BrowserDB.URLColumns; -import org.mozilla.gecko.gfx.BitmapUtils; -import org.mozilla.gecko.util.GamepadUtils; -import org.mozilla.gecko.util.GeckoEventListener; -import org.mozilla.gecko.util.StringUtils; -import org.mozilla.gecko.util.ThreadUtils; -import org.mozilla.gecko.util.UiAsyncTask; -import org.mozilla.gecko.widget.FaviconView; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.app.Activity; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.Log; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.LayoutInflater; -import android.view.MenuInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.TranslateAnimation; -import android.widget.AdapterView; -import android.widget.FilterQueryProvider; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.SimpleCursorAdapter; -import android.widget.TextView; - -import java.io.ByteArrayOutputStream; -import java.util.ArrayList; -import java.util.List; - -public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener { - public static final String LOGTAG = "GeckoAllPagesTab"; - private static final String TAG = "allPages"; - - 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; - private SuggestClient mSuggestClient; - private boolean mSuggestionsEnabled; - private AsyncTask<String, Void, ArrayList<String>> mSuggestTask; - private AwesomeBarCursorAdapter mCursorAdapter = null; - private boolean mTelemetrySent = false; - private LinearLayout mAllPagesView; - private boolean mAnimateSuggestions; - 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; - private static final int DELAY_SHOW_THUMBNAILS = 550; - - public AllPagesTab(Context context) { - super(context); - mSearchEngines = new ArrayList<SearchEngine>(); - - registerEventListener("SearchEngines:Data"); - GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null)); - - mHandler = new AllPagesHandler(); - } - - @Override - public boolean onBackPressed() { - return false; - } - - @Override - public int getTitleStringId() { - return R.string.awesomebar_all_pages_title; - } - - @Override - public String getTag() { - return TAG; - } - - private ListView getListView() { - if (mListView == null && mView != null) { - mListView = (ListView) mView.findViewById(R.id.awesomebar_list); - } - return mListView; - } - - @Override - public View getView() { - if (mView == null) { - mView = (LinearLayout) (LayoutInflater.from(mContext).inflate(R.layout.awesomebar_allpages_list, null)); - mView.setTag(TAG); - - final ListView list = getListView(); - list.setTag(TAG); - ((Activity)mContext).registerForContextMenu(list); - list.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - handleItemClick(parent, view, position, id); - } - }); - - AwesomeBarCursorAdapter adapter = getCursorAdapter(); - list.setAdapter(adapter); - list.setOnTouchListener(mListListener); - - final ListSelectionListener listener = new ListSelectionListener(); - list.setOnItemSelectedListener(listener); - list.setOnFocusChangeListener(listener); - - list.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, android.view.KeyEvent event) { - View selected = list.getSelectedView(); - - if (selected instanceof SearchEngineRow) { - return ((SearchEngineRow) selected).onKeyDown(keyCode, event); - } - return false; - } - }); - - } - - return mView; - } - - @Override - public void destroy() { - super.destroy(); - - unregisterEventListener("SearchEngines:Data"); - - mHandler.removeMessages(MESSAGE_UPDATE_FAVICONS); - mHandler.removeMessages(MESSAGE_LOAD_FAVICONS); - mHandler = null; - - // Can't use getters for adapter or listview. They will create them if null. - if (mCursorAdapter != null && mListView != null) { - mListView.setAdapter(null); - final Cursor cursor = mCursorAdapter.getCursor(); - // Gingerbread locks the DB when closing a cursor, so do it in the - // background. - ThreadUtils.postToBackgroundThread(new Runnable() { - @Override - public void run() { - if (cursor != null && !cursor.isClosed()) - cursor.close(); - } - }); - } - } - - public void filter(String searchTerm, AutocompleteHandler handler) { - mAutocompleteHandler = handler; - - AwesomeBarCursorAdapter adapter = getCursorAdapter(); - adapter.filter(searchTerm); - - filterSuggestions(searchTerm); - if (mSuggestionsOptInPrompt != null) { - int visibility = TextUtils.isEmpty(searchTerm) ? View.GONE : View.VISIBLE; - if (mSuggestionsOptInPrompt.getVisibility() != visibility) { - mSuggestionsOptInPrompt.setVisibility(visibility); - } - } - } - - 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. - */ - private void primeSuggestions() { - ThreadUtils.postToBackgroundThread(new Runnable() { - @Override - public void run() { - mSuggestClient.query(mSearchTerm); - } - }); - } - - private void filterSuggestions(String searchTerm) { - // cancel previous query - if (mSuggestTask != null) { - mSuggestTask.cancel(true); - } - - if (mSuggestClient != null && mSuggestionsEnabled) { - mSuggestTask = new AsyncTask<String, Void, ArrayList<String>>() { - @Override - protected ArrayList<String> doInBackground(String... query) { - return mSuggestClient.query(query[0]); - } - - @Override - protected void onPostExecute(ArrayList<String> suggestions) { - setSuggestions(suggestions); - } - }; - mSuggestTask.execute(searchTerm); - } - } - - protected AwesomeBarCursorAdapter getCursorAdapter() { - if (mCursorAdapter == null) { - // Load the list using a custom adapter so we can create the bitmaps - mCursorAdapter = new AwesomeBarCursorAdapter(mContext); - - mCursorAdapter.setFilterQueryProvider(new FilterQueryProvider() { - @Override - public Cursor runQuery(CharSequence constraint) { - long start = SystemClock.uptimeMillis(); - - Cursor c = BrowserDB.filter(getContentResolver(), constraint, MAX_RESULTS); - c.getCount(); - - postLoadFavicons(); - - long end = SystemClock.uptimeMillis(); - if (!mTelemetrySent && TextUtils.isEmpty(constraint)) { - int time = (int)(end - start); - Telemetry.HistogramAdd("FENNEC_AWESOMEBAR_ALLPAGES_EMPTY_TIME", time); - mTelemetrySent = true; - } - - findAutocompleteFor(constraint.toString(), c); - return c; - } - }); - } - return mCursorAdapter; - } - - private interface AwesomeBarItem { - public void onClick(); - public ContextMenuSubject getSubject(); - } - - private class AwesomeBarCursorItem implements AwesomeBarItem { - private Cursor mCursor; - - public AwesomeBarCursorItem(Cursor cursor) { - mCursor = cursor; - } - - @Override - public void onClick() { - String url = mCursor.getString(mCursor.getColumnIndexOrThrow(URLColumns.URL)); - String title = mCursor.getString(mCursor.getColumnIndexOrThrow(URLColumns.TITLE)); - sendToListener(url, title); - } - - @Override - public ContextMenuSubject getSubject() { - // Use the history id in order to allow removing history entries - int id = mCursor.getInt(mCursor.getColumnIndexOrThrow(Combined.HISTORY_ID)); - - String keyword = null; - int keywordCol = mCursor.getColumnIndex(URLColumns.KEYWORD); - if (keywordCol != -1) - keyword = mCursor.getString(keywordCol); - - final String url = mCursor.getString(mCursor.getColumnIndexOrThrow(URLColumns.URL)); - - Bitmap bitmap = Favicons.getInstance().getFaviconFromMemCache(url); - byte[] favicon = null; - - if (bitmap != null) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)) { - favicon = stream.toByteArray(); - } else { - Log.w(LOGTAG, "Favicon compression failed."); - } - } - - return new ContextMenuSubject(id, url, favicon, - mCursor.getString(mCursor.getColumnIndexOrThrow(URLColumns.TITLE)), - keyword, - mCursor.getInt(mCursor.getColumnIndexOrThrow(Combined.DISPLAY))); - } - } - - private class AwesomeBarSearchEngineItem implements AwesomeBarItem { - private SearchEngine mSearchEngine; - - public AwesomeBarSearchEngineItem(SearchEngine searchEngine) { - mSearchEngine = searchEngine; - } - - @Override - public void onClick() { - AwesomeBarTabs.OnUrlOpenListener listener = getUrlListener(); - if (listener != null) - listener.onSearch(mSearchEngine, mSearchTerm); - } - - @Override - public ContextMenuSubject getSubject() { - // Do not show context menu for search engine items - return null; - } - } - - private class AwesomeBarCursorAdapter extends SimpleCursorAdapter { - private static final int ROW_SEARCH = 0; - private static final int ROW_STANDARD = 1; - private static final int ROW_SUGGEST = 2; - - public AwesomeBarCursorAdapter(Context context) { - super(context, -1, null, new String[] {}, new int[] {}); - mSearchTerm = ""; - } - - public void filter(String searchTerm) { - boolean changed = !mSearchTerm.equals(searchTerm); - mSearchTerm = searchTerm; - - if (changed) - mCursorAdapter.notifyDataSetChanged(); - - getFilter().filter(searchTerm); - } - - private int getSuggestEngineCount() { - return (mSearchTerm.length() == 0 || mSuggestClient == null || !mSuggestionsEnabled) ? 0 : 1; - } - - // Add the search engines to the number of reported results. - @Override - public int getCount() { - final int resultCount = super.getCount(); - - // don't show search engines or suggestions if search field is empty - if (mSearchTerm.length() == 0) - return resultCount; - - return resultCount + mSearchEngines.size(); - } - - // If an item is part of the cursor result set, return that entry. - // Otherwise, return the search engine data. - @Override - public Object getItem(int position) { - int engineIndex = getEngineIndex(position); - - if (engineIndex == -1) { - // return awesomebar result - position -= getSuggestEngineCount(); - return new AwesomeBarCursorItem((Cursor) super.getItem(position)); - } - - // return search engine - return new AwesomeBarSearchEngineItem(mSearchEngines.get(engineIndex)); - } - - private int getEngineIndex(int position) { - final int resultCount = super.getCount(); - final int suggestEngineCount = getSuggestEngineCount(); - - // return suggest engine index - if (position < suggestEngineCount) - return position; - - // not an engine - if (position - suggestEngineCount < resultCount) - return -1; - - // return search engine index - return position - resultCount; - } - - @Override - public int getItemViewType(int position) { - int engine = getEngineIndex(position); - if (engine == -1) { - return ROW_STANDARD; - } else if (engine == 0 && mSuggestionsEnabled) { - // Give suggestion views their own type to prevent them from - // sharing other recycled search engine views. Using other - // recycled views for the suggestion row can break animations - // (bug 815937). - return ROW_SUGGEST; - } - return ROW_SEARCH; - } - - @Override - public int getViewTypeCount() { - // view can be either a standard awesomebar row, a search engine - // row, or a suggestion row - return 3; - } - - @Override - public boolean isEnabled(int position) { - // If we're using a gamepad or keyboard, allow the row to be - // focused so it can pass the focus to its child suggestion views. - if (!getListView().isInTouchMode()) { - return true; - } - - // If the suggestion row only contains one item (the user-entered - // query), allow the entire row to be clickable; clicking the row - // has the same effect as clicking the single suggestion. If the - // row contains multiple items, clicking the row will do nothing. - int index = getEngineIndex(position); - if (index != -1) - return mSearchEngines.get(index).suggestions.isEmpty(); - return true; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - int type = getItemViewType(position); - if (type == ROW_SEARCH || type == ROW_SUGGEST) { - if (convertView == null || !(convertView instanceof SearchEngineRow)) { - convertView = (SearchEngineRow) getInflater().inflate(R.layout.home_search_item_row, getListView(), false); - ((SearchEngineRow) convertView).setOnUrlOpenListener(getUrlListener()); - } - - SearchEngineRow searchRow = (SearchEngineRow) convertView; - searchRow.setSearchTerm(mSearchTerm); - - final SearchEngine engine = mSearchEngines.get(getEngineIndex(position)); - final boolean doAnimation = (mAnimateSuggestions && engine.suggestions.size() > 0); - searchRow.updateFromSearchEngine(engine, doAnimation); - if (doAnimation) { - // Only animate suggestions the first time they are shown - mAnimateSuggestions = false; - } - } else { - AwesomeEntryViewHolder viewHolder = null; - - if (convertView == null || !(convertView.getTag() instanceof AwesomeEntryViewHolder)) { - convertView = getInflater().inflate(R.layout.awesomebar_row, null); - - viewHolder = new AwesomeEntryViewHolder(); - viewHolder.titleView = (TextView) convertView.findViewById(R.id.title); - viewHolder.urlView = (TextView) convertView.findViewById(R.id.url); - viewHolder.faviconView = (FaviconView) convertView.findViewById(R.id.favicon); - viewHolder.bookmarkIconView = (ImageView) convertView.findViewById(R.id.bookmark_icon); - - convertView.setTag(viewHolder); - } else { - viewHolder = (AwesomeEntryViewHolder) convertView.getTag(); - } - - position -= getSuggestEngineCount(); - Cursor cursor = getCursor(); - if (!cursor.moveToPosition(position)) - throw new IllegalStateException("Couldn't move cursor to position " + position); - - updateTitle(viewHolder.titleView, cursor); - updateUrl(viewHolder, cursor); - updateBookmarkIcon(viewHolder.bookmarkIconView, cursor); - displayFavicon(viewHolder); - } - - return convertView; - } - }; - - /** - * Sets suggestions associated with the current suggest engine. - * If there is no suggest engine, this does nothing. - */ - private void setSuggestions(final ArrayList<String> suggestions) { - if (mSuggestClient != null) { - mSearchEngines.get(0).suggestions = suggestions; - getCursorAdapter().notifyDataSetChanged(); - } - } - - /** - * Sets search engines to be shown for user-entered queries. - */ - private void setSearchEngines(JSONObject data) { - try { - JSONObject suggest = data.getJSONObject("suggest"); - String suggestEngine = suggest.isNull("engine") ? null : suggest.getString("engine"); - String suggestTemplate = suggest.isNull("template") ? null : suggest.getString("template"); - mSuggestionsEnabled = suggest.getBoolean("enabled"); - boolean suggestionsPrompted = suggest.getBoolean("prompted"); - JSONArray engines = data.getJSONArray("searchEngines"); - - ArrayList<SearchEngine> searchEngines = new ArrayList<SearchEngine>(); - for (int i = 0; i < engines.length(); i++) { - JSONObject engineJSON = engines.getJSONObject(i); - String name = engineJSON.getString("name"); - String identifier = engineJSON.getString("identifier"); - String iconURI = engineJSON.getString("iconURI"); - Bitmap icon = BitmapUtils.getBitmapFromDataURI(iconURI); - if (name.equals(suggestEngine) && suggestTemplate != null) { - // suggest engine should be at the front of the list - searchEngines.add(0, new SearchEngine(name, identifier, icon)); - - // The only time Tabs.getInstance().getSelectedTab() should - // be null is when we're restoring after a crash. We should - // never restore private tabs when that happens, so it - // should be safe to assume that null means non-private. - Tab tab = Tabs.getInstance().getSelectedTab(); - if (tab == null || !tab.isPrivate()) - mSuggestClient = new SuggestClient(getView().getContext(), suggestTemplate, SUGGESTION_TIMEOUT, SUGGESTION_MAX); - } else { - searchEngines.add(new SearchEngine(name, identifier, icon)); - } - } - - mSearchEngines = searchEngines; - mCursorAdapter.notifyDataSetChanged(); - - // show suggestions opt-in if user hasn't been prompted - if (!suggestionsPrompted && mSuggestClient != null) { - showSuggestionsOptIn(); - } - } catch (JSONException e) { - Log.e(LOGTAG, "Error getting search engine JSON", e); - } - - filterSuggestions(mSearchTerm); - } - - private void showSuggestionsOptIn() { - mSuggestionsOptInPrompt = LayoutInflater.from(mContext).inflate(R.layout.awesomebar_suggestion_prompt, (LinearLayout)getView(), false); - GeckoTextView promptText = (GeckoTextView) mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_title); - promptText.setText(getResources().getString(R.string.suggestions_prompt, mSearchEngines.get(0).name)); - Tab tab = Tabs.getInstance().getSelectedTab(); - if (tab != null) - promptText.setPrivateMode(tab.isPrivate()); - - final View yesButton = mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_yes); - final View noButton = mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_no); - OnClickListener listener = new OnClickListener() { - @Override - public void onClick(View v) { - // Prevent the buttons from being clicked multiple times (bug 816902) - yesButton.setOnClickListener(null); - noButton.setOnClickListener(null); - - setSuggestionsEnabled(v == yesButton); - } - }; - yesButton.setOnClickListener(listener); - noButton.setOnClickListener(listener); - - // If the prompt container gains focus, automatically pass focus to the - // yes button in the prompt. - final View promptContainer = mSuggestionsOptInPrompt.findViewById(R.id.prompt_container); - promptContainer.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - yesButton.requestFocus(); - } - } - }); - - mSuggestionsOptInPrompt.setVisibility(View.GONE); - ((LinearLayout)getView()).addView(mSuggestionsOptInPrompt, 0); - } - - private void setSuggestionsEnabled(final boolean enabled) { - // Clicking the yes/no buttons quickly can cause the click events be - // queued before the listeners are removed above, so it's possible - // setSuggestionsEnabled() can be called twice. mSuggestionsOptInPrompt - // can be null if this happens (bug 828480). - if (mSuggestionsOptInPrompt == null) { - return; - } - - // Make suggestions appear immediately after the user opts in - primeSuggestions(); - - // Pref observer in gecko will also set prompted = true - PrefsHelper.setPref("browser.search.suggest.enabled", enabled); - - TranslateAnimation anim1 = new TranslateAnimation(0, mSuggestionsOptInPrompt.getWidth(), 0, 0); - anim1.setDuration(ANIMATION_DURATION); - anim1.setInterpolator(new AccelerateInterpolator()); - anim1.setFillAfter(true); - final View promptContainer = mSuggestionsOptInPrompt.findViewById(R.id.prompt_container); - - TranslateAnimation anim2 = new TranslateAnimation(0, 0, 0, -1 * mSuggestionsOptInPrompt.getHeight()); - anim2.setDuration(ANIMATION_DURATION); - anim2.setFillAfter(true); - anim2.setStartOffset(anim1.getDuration()); - final LinearLayout view = (LinearLayout)getView(); - anim2.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation a) { - // Increase the height of the view so a gap isn't shown during animation - view.getLayoutParams().height = view.getHeight() + - mSuggestionsOptInPrompt.getHeight(); - view.requestLayout(); - } - @Override - public void onAnimationRepeat(Animation a) {} - @Override - public void onAnimationEnd(Animation a) { - // Removing the view immediately results in a NPE in - // dispatchDraw(), possibly because this callback executes - // before drawing is finished. Posting this as a Runnable fixes - // the issue. - view.post(new Runnable() { - @Override - public void run() { - view.removeView(mSuggestionsOptInPrompt); - getListView().clearAnimation(); - mSuggestionsOptInPrompt = null; - - if (enabled) { - // Reset the view height - view.getLayoutParams().height = LayoutParams.FILL_PARENT; - - mSuggestionsEnabled = enabled; - mAnimateSuggestions = true; - getCursorAdapter().notifyDataSetChanged(); - filterSuggestions(mSearchTerm); - } - } - }); - } - }); - - promptContainer.startAnimation(anim1); - mSuggestionsOptInPrompt.startAnimation(anim2); - getListView().startAnimation(anim2); - } - - @Override - public void handleMessage(String event, final JSONObject message) { - if (event.equals("SearchEngines:Data")) { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - setSearchEngines(message); - } - }); - } - } - - public void handleItemClick(AdapterView<?> parent, View view, int position, long id) { - ListView listview = getListView(); - if (listview == null) - return; - - AwesomeBarItem item = (AwesomeBarItem)listview.getItemAtPosition(position); - item.onClick(); - } - - protected void updateBookmarkIcon(ImageView bookmarkIconView, Cursor cursor) { - int bookmarkIdIndex = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID); - long id = cursor.getLong(bookmarkIdIndex); - - int displayIndex = cursor.getColumnIndexOrThrow(Combined.DISPLAY); - int display = cursor.getInt(displayIndex); - - // The bookmark id will be 0 (null in database) when the url - // is not a bookmark. - int visibility = (id == 0 ? View.GONE : View.VISIBLE); - bookmarkIconView.setVisibility(visibility); - - if (display == Combined.DISPLAY_READER) { - bookmarkIconView.setImageResource(R.drawable.ic_awesomebar_reader); - } else { - bookmarkIconView.setImageResource(R.drawable.ic_awesomebar_star); - } - } - - @Override - public ContextMenuSubject getSubject(ContextMenu menu, View view, ContextMenuInfo menuInfo) { - ContextMenuSubject subject = null; - - if (!(menuInfo instanceof AdapterView.AdapterContextMenuInfo)) { - Log.e(LOGTAG, "menuInfo is not AdapterContextMenuInfo"); - return subject; - } - - ListView list = (ListView)view; - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - subject = ((AwesomeBarItem) list.getItemAtPosition(info.position)).getSubject(); - - if (subject == null) - return subject; - - setupMenu(menu, subject); - - menu.findItem(R.id.remove_bookmark).setVisible(false); - menu.findItem(R.id.edit_bookmark).setVisible(false); - menu.findItem(R.id.open_in_reader).setVisible(subject.display == Combined.DISPLAY_READER); - - return subject; - } - - private void registerEventListener(String event) { - GeckoAppShell.getEventDispatcher().registerEventListener(event, this); - } - - private void unregisterEventListener(String event) { - GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this); - } - - private List<String> getUrlsWithoutFavicon() { - List<String> urls = new ArrayList<String>(); - - Cursor c = mCursorAdapter.getCursor(); - if (c == null || !c.moveToFirst()) - return urls; - - do { - final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL)); - - // We only want to load favicons from DB if they are not in the - // memory cache yet. - if (Favicons.getInstance().getFaviconFromMemCache(url) != null) - continue; - - urls.add(url); - } while (c.moveToNext()); - - return urls; - } - - public void storeFaviconsInMemCache(Cursor c) { - if (c == null) - return; - - try { - if (!c.moveToFirst()) - return; - - do { - final String url = c.getString(c.getColumnIndexOrThrow(Combined.URL)); - final byte[] b = c.getBlob(c.getColumnIndexOrThrow(Combined.FAVICON)); - if (b == null) - continue; - - Bitmap favicon = BitmapUtils.decodeByteArray(b); - if (favicon == null) - continue; - - favicon = Favicons.getInstance().scaleImage(favicon); - Favicons.getInstance().putFaviconInMemCache(url, favicon); - } while (c.moveToNext()); - } finally { - c.close(); - } - } - - private void loadFaviconsForCurrentResults() { - final List<String> urls = getUrlsWithoutFavicon(); - if (urls.size() == 0) - return; - - (new UiAsyncTask<Void, Void, Void>(ThreadUtils.getBackgroundHandler()) { - @Override - public Void doInBackground(Void... params) { - Cursor cursor = BrowserDB.getFaviconsForUrls(getContentResolver(), urls); - storeFaviconsInMemCache(cursor); - return null; - } - - @Override - public void onPostExecute(Void result) { - postUpdateFavicons(); - } - }).execute(); - } - - private void displayFavicon(AwesomeEntryViewHolder viewHolder) { - final String url = viewHolder.url; - Bitmap bitmap = Favicons.getInstance().getFaviconFromMemCache(url); - updateFavicon(viewHolder.faviconView, bitmap, url); - } - - private void updateFavicons() { - ListView listView = getListView(); - AwesomeBarCursorAdapter adapter = getCursorAdapter(); - Cursor cursor = adapter.getCursor(); - if (cursor == null) - return; - - for (int i = 0; i < listView.getChildCount(); i++) { - final View view = listView.getChildAt(i); - final Object tag = view.getTag(); - - if (tag == null || !(tag instanceof AwesomeEntryViewHolder)) - continue; - - final AwesomeEntryViewHolder viewHolder = (AwesomeEntryViewHolder) tag; - displayFavicon(viewHolder); - } - - mView.invalidate(); - } - - private void postUpdateFavicons() { - if (mHandler == null) - return; - - Message msg = mHandler.obtainMessage(MESSAGE_UPDATE_FAVICONS, - AllPagesTab.this); - - mHandler.removeMessages(MESSAGE_UPDATE_FAVICONS); - mHandler.sendMessage(msg); - } - - private void postLoadFavicons() { - if (mHandler == null) - return; - - Message msg = mHandler.obtainMessage(MESSAGE_LOAD_FAVICONS, - AllPagesTab.this); - - mHandler.removeMessages(MESSAGE_LOAD_FAVICONS); - mHandler.sendMessageDelayed(msg, 200); - } - - private class AllPagesHandler extends Handler { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_LOAD_FAVICONS: - loadFaviconsForCurrentResults(); - break; - case MESSAGE_UPDATE_FAVICONS: - updateFavicons(); - break; - } - } - } - - private static class ListSelectionListener implements View.OnFocusChangeListener, - AdapterView.OnItemSelectedListener { - private SearchEngineRow mSelectedEngineRow; - - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - View selectedRow = ((ListView) v).getSelectedView(); - if (selectedRow != null) { - selectRow(selectedRow); - } - } else { - deselectRow(); - } - } - - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - deselectRow(); - selectRow(view); - } - - @Override - public void onNothingSelected(AdapterView<?> parent) { - deselectRow(); - } - - private void selectRow(View row) { - if (row instanceof SearchEngineRow) { - mSelectedEngineRow = (SearchEngineRow) row; - mSelectedEngineRow.onSelected(); - } - } - - private void deselectRow() { - if (mSelectedEngineRow != null) { - mSelectedEngineRow.onDeselected(); - mSelectedEngineRow = null; - } - } - } -}
deleted file mode 100644 --- a/mobile/android/base/awesomebar/AwesomeBarTab.java +++ /dev/null @@ -1,194 +0,0 @@ -/* -*- 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.AwesomeBar.ContextMenuSubject; -import org.mozilla.gecko.db.BrowserDB.URLColumns; -import org.mozilla.gecko.gfx.BitmapUtils; -import org.mozilla.gecko.widget.FaviconView; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.text.TextUtils; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.LayoutInflater; -import android.view.MenuInflater; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.widget.ImageView; -import android.widget.TextView; - -import java.util.HashMap; - -abstract public class AwesomeBarTab { - abstract public String getTag(); - abstract public int getTitleStringId(); - abstract public boolean onBackPressed(); - abstract public ContextMenuSubject getSubject(ContextMenu menu, View view, ContextMenuInfo menuInfo); - abstract public View getView(); - - protected View mView = null; - protected View.OnTouchListener mListListener; - private AwesomeBarTabs.OnUrlOpenListener mListener; - private LayoutInflater mInflater = null; - private ContentResolver mContentResolver = null; - private Resources mResources; - // FIXME: This value should probably come from a prefs key - public static final int MAX_RESULTS = 100; - protected Context mContext = null; - public static HashMap<String, Integer> sOpenTabs; - - public AwesomeBarTab(Context context) { - mContext = context; - } - - public void destroy() { - sOpenTabs = null; - } - - public void setListTouchListener(View.OnTouchListener listener) { - mListListener = listener; - if (mView != null) - mView.setOnTouchListener(mListListener); - } - - protected class AwesomeEntryViewHolder { - public TextView titleView; - public String url; - public TextView urlView; - public FaviconView faviconView; - public ImageView bookmarkIconView; - } - - protected LayoutInflater getInflater() { - if (mInflater == null) { - mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - return mInflater; - } - - protected AwesomeBarTabs.OnUrlOpenListener getUrlListener() { - return mListener; - } - - protected void setUrlListener(AwesomeBarTabs.OnUrlOpenListener listener) { - mListener = listener; - } - - protected ContentResolver getContentResolver() { - if (mContentResolver == null) { - mContentResolver = mContext.getContentResolver(); - } - return mContentResolver; - } - - protected HashMap<String, Integer> getOpenTabs() { - if (sOpenTabs == null || sOpenTabs.isEmpty()) { - Iterable<Tab> tabs = Tabs.getInstance().getTabsInOrder(); - sOpenTabs = new HashMap<String, Integer>(); - for (Tab tab : tabs) { - sOpenTabs.put(tab.getURL(), tab.getId()); - } - } - return sOpenTabs; - } - - protected Resources getResources() { - if (mResources == null) { - mResources = mContext.getResources(); - } - return mResources; - } - - protected void setupMenu(ContextMenu menu, AwesomeBar.ContextMenuSubject subject) { - MenuInflater inflater = new MenuInflater(mContext); - inflater.inflate(R.menu.awesomebar_contextmenu, menu); - - // Show Open Private Tab if we're in private mode, Open New Tab otherwise - boolean isPrivate = false; - Tab tab = Tabs.getInstance().getSelectedTab(); - if (tab != null) { - isPrivate = tab.isPrivate(); - } - menu.findItem(R.id.open_new_tab).setVisible(!isPrivate); - menu.findItem(R.id.open_private_tab).setVisible(isPrivate); - - // Hide "Remove" item if there isn't a valid history ID - if (subject.id < 0) { - menu.findItem(R.id.remove_history).setVisible(false); - } - menu.setHeaderTitle(subject.title); - } - - protected void updateFavicon(FaviconView faviconView, Bitmap bitmap, String key) { - faviconView.updateImage(bitmap, key); - } - - protected void updateTitle(TextView titleView, Cursor cursor) { - int titleIndex = cursor.getColumnIndexOrThrow(URLColumns.TITLE); - String title = cursor.getString(titleIndex); - String url = ""; - - // Use the URL instead of an empty title for consistency with the normal URL - // bar view - this is the equivalent of getDisplayTitle() in Tab.java - if (TextUtils.isEmpty(title)) { - int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL); - url = cursor.getString(urlIndex); - } - - updateTitle(titleView, title, url); - } - - protected void updateTitle(TextView titleView, String title, String url) { - if (TextUtils.isEmpty(title)) { - titleView.setText(url); - } else { - titleView.setText(title); - } - } - - public void sendToListener(String url, String title) { - AwesomeBarTabs.OnUrlOpenListener listener = getUrlListener(); - if (listener == null) - return; - - Integer tabId = getOpenTabs().get(url); - if (tabId != null) { - listener.onSwitchToTab(tabId); - } else { - listener.onUrlOpen(url, title); - } - } - - protected void updateUrl(AwesomeEntryViewHolder holder, Cursor cursor) { - int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL); - String url = cursor.getString(urlIndex); - updateUrl(holder, url); - } - - protected void updateUrl(AwesomeEntryViewHolder holder, String url) { - Integer tabId = getOpenTabs().get(url); - holder.url = url; - if (tabId != null) { - holder.urlView.setText(R.string.awesomebar_switch_to_tab); - holder.urlView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_awesomebar_tab, 0, 0, 0); - } else { - holder.urlView.setText(url); - holder.urlView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - } - } - - protected boolean hideSoftInput(View view) { - InputMethodManager imm = - (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); - - return imm.hideSoftInputFromWindow(view.getWindowToken(), 0); - } -}
deleted file mode 100644 --- a/mobile/android/base/awesomebar/BookmarksTab.java +++ /dev/null @@ -1,470 +0,0 @@ -/* -*- 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.AwesomeBar.ContextMenuSubject; -import org.mozilla.gecko.db.BrowserContract.Bookmarks; -import org.mozilla.gecko.db.BrowserContract.Combined; -import org.mozilla.gecko.db.BrowserDB; -import org.mozilla.gecko.db.BrowserDB.URLColumns; -import org.mozilla.gecko.gfx.BitmapUtils; -import org.mozilla.gecko.util.GamepadUtils; -import org.mozilla.gecko.util.ThreadUtils; -import org.mozilla.gecko.widget.FaviconView; - -import android.app.Activity; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.os.AsyncTask; -import android.util.Log; -import android.util.Pair; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.LayoutInflater; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ListView; -import android.widget.SimpleCursorAdapter; -import android.widget.TextView; - -import java.util.LinkedList; - -public class BookmarksTab extends AwesomeBarTab { - public static final String LOGTAG = "BOOKMARKS_TAB"; - public static final String TAG = "bookmarks"; - private int mFolderId; - private String mFolderTitle; - private BookmarksListAdapter mCursorAdapter = null; - private BookmarksQueryTask mQueryTask = null; - private boolean mShowReadingList = false; - - @Override - public int getTitleStringId() { - return R.string.awesomebar_bookmarks_title; - } - - @Override - public String getTag() { - return TAG; - } - - public BookmarksTab(Context context) { - super(context); - } - - @Override - public View getView() { - if (mView == null) { - mView = new ListView(mContext, null); - ((Activity)mContext).registerForContextMenu(mView); - mView.setTag(TAG); - mView.setOnTouchListener(mListListener); - - // We need to add the header before we set the adapter, hence make it null - ListView list = (ListView)mView; - list.setAdapter(null); - list.setAdapter(getCursorAdapter()); - list.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - handleItemClick(parent, view, position, id); - } - }); - - if (mShowReadingList) { - String title = getResources().getString(R.string.bookmarks_folder_reading_list); - getCursorAdapter().moveToChildFolder(Bookmarks.FIXED_READING_LIST_ID, title); - } else { - BookmarksQueryTask task = getQueryTask(); - task.execute(); - } - } - return (ListView)mView; - } - - public void setShowReadingList(boolean showReadingList) { - mShowReadingList = showReadingList; - } - - @Override - public void destroy() { - super.destroy(); - // Can't use getters for adapter. It will create one if null. - if (mCursorAdapter != null && mView != null) { - ListView list = (ListView)mView; - list.setAdapter(null); - final Cursor cursor = mCursorAdapter.getCursor(); - // Gingerbread locks the DB when closing a cursor, so do it in the - // background. - ThreadUtils.postToBackgroundThread(new Runnable() { - @Override - public void run() { - if (cursor != null && !cursor.isClosed()) - cursor.close(); - } - }); - } - } - - @Override - public boolean onBackPressed() { - // If the soft keyboard is visible in the bookmarks or history tab, the user - // must have explictly brought it up, so we should try hiding it instead of - // exiting the activity or going up a bookmarks folder level. - if (hideSoftInput(getView())) - return true; - - return moveToParentFolder(); - } - - protected BookmarksListAdapter getCursorAdapter() { - return getCursorAdapter(null); - } - - protected BookmarksListAdapter getCursorAdapter(Cursor c) { - if (mCursorAdapter == null) { - mCursorAdapter = new BookmarksListAdapter(mContext, c); - } else if (c != null) { - mCursorAdapter.changeCursor(c); - } else { - // do a quick return if just asking for the cached adapter - return mCursorAdapter; - } - - TextView headerView = mCursorAdapter.getHeaderView(); - if (headerView == null) { - headerView = (TextView) getInflater().inflate(R.layout.awesomebar_header_row, null); - mCursorAdapter.setHeaderView(headerView); - } - - // Add/Remove header based on the root folder - if (mView != null) { - ListView list = (ListView)mView; - if (mFolderId == Bookmarks.FIXED_ROOT_ID) { - if (list.getHeaderViewsCount() == 1) { - list.removeHeaderView(headerView); - } - } else { - if (list.getHeaderViewsCount() == 0) { - list.addHeaderView(headerView, null, true); - } - headerView.setText(mFolderTitle); - } - } - - return mCursorAdapter; - } - - protected BookmarksQueryTask getQueryTask() { - if (mQueryTask == null) { - mQueryTask = new BookmarksQueryTask(); - } - return mQueryTask; - } - - public void handleItemClick(AdapterView<?> parent, View view, int position, long id) { - ListView list = (ListView)getView(); - if (list == null) - return; - - int headerCount = list.getHeaderViewsCount(); - // If we tap on the header view, there's nothing to do - if (headerCount == 1 && position == 0) - return; - - BookmarksListAdapter adapter = getCursorAdapter(); - if (adapter == null) - return; - - Cursor cursor = adapter.getCursor(); - if (cursor == null) - return; - - // The header view takes up a spot in the list - if (headerCount == 1) - position--; - - cursor.moveToPosition(position); - - int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE)); - if (type == Bookmarks.TYPE_FOLDER) { - // If we're clicking on a folder, update adapter to move to that folder - int folderId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID)); - String folderTitle = adapter.getFolderTitle(position); - - adapter.moveToChildFolder(folderId, folderTitle); - return; - } - - // Otherwise, just open the URL - String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)); - String title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE)); - long parentId = cursor.getLong(cursor.getColumnIndexOrThrow(Bookmarks.PARENT)); - if (parentId == Bookmarks.FIXED_READING_LIST_ID) { - url = ReaderModeUtils.getAboutReaderForUrl(url, true); - } - sendToListener(url, title); - } - - private class BookmarksListAdapter extends SimpleCursorAdapter { - private static final int VIEW_TYPE_ITEM = 0; - private static final int VIEW_TYPE_FOLDER = 1; - private static final int VIEW_TYPE_COUNT = 2; - - private LinkedList<Pair<Integer, String>> mParentStack; - private TextView mBookmarksTitleView; - - public BookmarksListAdapter(Context context, Cursor c) { - super(context, -1, c, new String[] {}, new int[] {}); - - // mParentStack holds folder id/title pairs that allow us to navigate - // back up the folder heirarchy - mParentStack = new LinkedList<Pair<Integer, String>>(); - - // Add the root folder to the stack - Pair<Integer, String> rootFolder = new Pair<Integer, String>(Bookmarks.FIXED_ROOT_ID, ""); - mParentStack.addFirst(rootFolder); - } - - public void refreshCurrentFolder() { - // Cancel any pre-existing async refresh tasks - if (mQueryTask != null) - mQueryTask.cancel(false); - - Pair<Integer, String> folderPair = mParentStack.getFirst(); - mQueryTask = new BookmarksQueryTask(folderPair.first, folderPair.second); - mQueryTask.execute(); - } - - // Returns false if there is no parent folder to move to - public boolean moveToParentFolder() { - // If we're already at the root, we can't move to a parent folder - if (mParentStack.size() == 1) - return false; - - mParentStack.removeFirst(); - refreshCurrentFolder(); - return true; - } - - public void moveToChildFolder(int folderId, String folderTitle) { - Pair<Integer, String> folderPair = new Pair<Integer, String>(folderId, folderTitle); - mParentStack.addFirst(folderPair); - refreshCurrentFolder(); - } - - public boolean isInReadingList() { - Pair<Integer, String> folderPair = mParentStack.getFirst(); - return (folderPair.first == Bookmarks.FIXED_READING_LIST_ID); - } - - @Override - public int getItemViewType(int position) { - Cursor c = getCursor(); - - if (c.moveToPosition(position) && - c.getInt(c.getColumnIndexOrThrow(Bookmarks.TYPE)) == Bookmarks.TYPE_FOLDER) - return VIEW_TYPE_FOLDER; - - // Default to retuning normal item type - return VIEW_TYPE_ITEM; - } - - @Override - public int getViewTypeCount() { - return VIEW_TYPE_COUNT; - } - - public String getFolderTitle(int position) { - Cursor c = getCursor(); - if (!c.moveToPosition(position)) - return ""; - - String guid = c.getString(c.getColumnIndexOrThrow(Bookmarks.GUID)); - - // If we don't have a special GUID, just return the folder title from the DB. - if (guid == null || guid.length() == 12) - return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE)); - - // Use localized strings for special folder names. - if (guid.equals(Bookmarks.FAKE_DESKTOP_FOLDER_GUID)) - return getResources().getString(R.string.bookmarks_folder_desktop); - else if (guid.equals(Bookmarks.MENU_FOLDER_GUID)) - return getResources().getString(R.string.bookmarks_folder_menu); - else if (guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID)) - return getResources().getString(R.string.bookmarks_folder_toolbar); - else if (guid.equals(Bookmarks.UNFILED_FOLDER_GUID)) - return getResources().getString(R.string.bookmarks_folder_unfiled); - else if (guid.equals(Bookmarks.READING_LIST_FOLDER_GUID)) - return getResources().getString(R.string.bookmarks_folder_reading_list); - - // If for some reason we have a folder with a special GUID, but it's not one of - // the special folders we expect in the UI, just return the title from the DB. - return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE)); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - int viewType = getItemViewType(position); - AwesomeEntryViewHolder viewHolder = null; - - if (convertView == null) { - if (viewType == VIEW_TYPE_ITEM) - convertView = getInflater().inflate(R.layout.awesomebar_row, null); - else - convertView = getInflater().inflate(R.layout.awesomebar_folder_row, null); - - viewHolder = new AwesomeEntryViewHolder(); - viewHolder.titleView = (TextView) convertView.findViewById(R.id.title); - viewHolder.faviconView = (FaviconView) convertView.findViewById(R.id.favicon); - - if (viewType == VIEW_TYPE_ITEM) - viewHolder.urlView = (TextView) convertView.findViewById(R.id.url); - - convertView.setTag(viewHolder); - } else { - viewHolder = (AwesomeEntryViewHolder) convertView.getTag(); - } - - Cursor cursor = getCursor(); - if (!cursor.moveToPosition(position)) - throw new IllegalStateException("Couldn't move cursor to position " + position); - - if (viewType == VIEW_TYPE_ITEM) { - updateTitle(viewHolder.titleView, cursor); - updateUrl(viewHolder, cursor); - - byte[] b = cursor.getBlob(cursor.getColumnIndexOrThrow(URLColumns.FAVICON)); - Bitmap favicon = null; - if (b != null) { - Bitmap bitmap = BitmapUtils.decodeByteArray(b); - if (bitmap != null) { - favicon = Favicons.getInstance().scaleImage(bitmap); - } - } - String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)); - updateFavicon(viewHolder.faviconView, favicon, url); - } else { - viewHolder.titleView.setText(getFolderTitle(position)); - } - - return convertView; - } - - public TextView getHeaderView() { - return mBookmarksTitleView; - } - - public void setHeaderView(TextView titleView) { - mBookmarksTitleView = titleView; - } - } - - private class BookmarksQueryTask extends AsyncTask<Void, Void, Cursor> { - public BookmarksQueryTask() { - mFolderId = Bookmarks.FIXED_ROOT_ID; - mFolderTitle = ""; - } - - public BookmarksQueryTask(int folderId, String folderTitle) { - mFolderId = folderId; - mFolderTitle = folderTitle; - } - - @Override - protected Cursor doInBackground(Void... arg0) { - return BrowserDB.getBookmarksInFolder(getContentResolver(), mFolderId); - } - - @Override - protected void onPostExecute(final Cursor cursor) { - // Hack: force this to the main thread, even though it should already be on it - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - // this will update the cursorAdapter to use the new one if it already exists - // We need to add the header before we set the adapter, hence make it null - ListView list = (ListView)mView; - list.setAdapter(null); - list.setAdapter(getCursorAdapter(cursor)); - } - }); - mQueryTask = null; - } - } - - public boolean moveToParentFolder() { - // If we're not in the bookmarks tab, we have nothing to do. We should - // also return false if mBookmarksAdapter hasn't been initialized yet. - BookmarksListAdapter adapter = getCursorAdapter(); - if (adapter == null) - return false; - - return adapter.moveToParentFolder(); - } - - /** - * Whether the user is in the Reading List bookmarks directory in the - * AwesomeScreen UI. - */ - public boolean isInReadingList() { - if (mCursorAdapter == null) - return false; - - return mCursorAdapter.isInReadingList(); - } - - @Override - public ContextMenuSubject getSubject(ContextMenu menu, View view, ContextMenuInfo menuInfo) { - ContextMenuSubject subject = null; - - if (!(menuInfo instanceof AdapterView.AdapterContextMenuInfo)) { - Log.e(LOGTAG, "menuInfo is not AdapterContextMenuInfo"); - return subject; - } - - ListView list = (ListView)view; - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Object selectedItem = list.getItemAtPosition(info.position); - - if (!(selectedItem instanceof Cursor)) { - Log.e(LOGTAG, "item at " + info.position + " is not a Cursor"); - return subject; - } - - Cursor cursor = (Cursor) selectedItem; - - // Don't show the context menu for folders - if (!(cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE)) == Bookmarks.TYPE_FOLDER)) { - String keyword = null; - int keywordCol = cursor.getColumnIndex(URLColumns.KEYWORD); - if (keywordCol != -1) - keyword = cursor.getString(keywordCol); - - int id = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID)); - - subject = new ContextMenuSubject(id, - cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)), - cursor.getBlob(cursor.getColumnIndexOrThrow(URLColumns.FAVICON)), - cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE)), - keyword, - isInReadingList() ? Combined.DISPLAY_READER : Combined.DISPLAY_NORMAL); - } - - if (subject == null) - return subject; - - setupMenu(menu, subject); - - menu.findItem(R.id.remove_history).setVisible(false); - menu.findItem(R.id.open_in_reader).setVisible(false); - - return subject; - } -}
deleted file mode 100644 --- a/mobile/android/base/awesomebar/HistoryTab.java +++ /dev/null @@ -1,455 +0,0 @@ -/* -*- 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.AwesomeBar.ContextMenuSubject; -import org.mozilla.gecko.db.BrowserContract.Combined; -import org.mozilla.gecko.db.BrowserDB; -import org.mozilla.gecko.db.BrowserDB.URLColumns; -import org.mozilla.gecko.gfx.BitmapUtils; -import org.mozilla.gecko.util.GamepadUtils; -import org.mozilla.gecko.util.ThreadUtils; -import org.mozilla.gecko.widget.FaviconView; - -import android.app.Activity; -import android.content.ContentResolver; -import android.content.Context; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.os.AsyncTask; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.KeyEvent; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ExpandableListView; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.SimpleExpandableListAdapter; -import android.widget.TextView; - -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -public class HistoryTab extends AwesomeBarTab { - public static final String LOGTAG = "HISTORY_TAB"; - public static final String TAG = "history"; - private static enum HistorySection { TODAY, YESTERDAY, WEEK, OLDER }; - private ContentObserver mContentObserver; - private ContentResolver mContentResolver; - private HistoryQueryTask mQueryTask = null; - private HistoryListAdapter mCursorAdapter = null; - - public HistoryTab(Context context) { - super(context); - mContentObserver = null; - } - - @Override - public int getTitleStringId() { - return R.string.awesomebar_history_title; - } - - @Override - public String getTag() { - return TAG; - } - - @Override - public ListView getView() { - if (mView == null) { - mView = new ExpandableListView(mContext, null); - ((Activity)mContext).registerForContextMenu(mView); - mView.setTag(TAG); - - ExpandableListView list = (ExpandableListView)mView; - list.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { - @Override - public boolean onChildClick(ExpandableListView parent, View view, - int groupPosition, int childPosition, long id) { - return handleItemClick(groupPosition, childPosition); - } - }); - - // This is to disallow collapsing the expandable groups in the - // history expandable list view to mimic simpler sections. We should - // Remove this if we decide to allow expanding/collapsing groups. - list.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() { - @Override - public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { - return true; - } - }); - list.setOnKeyListener(new View.OnKeyListener() { - @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - if (GamepadUtils.isActionKeyDown(event)) { - ExpandableListView expando = (ExpandableListView)v; - long selected = expando.getSelectedPosition(); - switch (ExpandableListView.getPackedPositionType(selected)) { - case ExpandableListView.PACKED_POSITION_TYPE_CHILD: - return handleItemClick(ExpandableListView.getPackedPositionGroup(selected), - ExpandableListView.getPackedPositionChild(selected)); - case ExpandableListView.PACKED_POSITION_TYPE_GROUP: - int group = ExpandableListView.getPackedPositionGroup(selected); - return (expando.isGroupExpanded(group) - ? expando.collapseGroup(group) - : expando.expandGroup(group)); - } - } - return false; - } - }); - - mView.setOnTouchListener(mListListener); - - // We need to add the header before we set the adapter, hence make it null - list.setAdapter(getCursorAdapter()); - HistoryQueryTask task = new HistoryQueryTask(); - task.execute(); - } - return (ListView)mView; - } - - @Override - public void destroy() { - super.destroy(); - - if (mContentObserver != null) - BrowserDB.unregisterContentObserver(getContentResolver(), mContentObserver); - } - - @Override - public boolean onBackPressed() { - // If the soft keyboard is visible in the bookmarks or history tab, the user - // must have explictly brought it up, so we should try hiding it instead of - // exiting the activity or going up a bookmarks folder level. - View view = getView(); - if (hideSoftInput(view)) - return true; - - return false; - } - - protected HistoryListAdapter getCursorAdapter() { - return mCursorAdapter; - } - - private class HistoryListAdapter extends SimpleExpandableListAdapter { - public HistoryListAdapter(Context context, List<? extends Map<String, ?>> groupData, - int groupLayout, String[] groupFrom, int[] groupTo, - List<? extends List<? extends Map<String, ?>>> childData) { - - super(context, groupData, groupLayout, groupFrom, groupTo, - childData, -1, new String[] {}, new int[] {}); - } - - @Override - public View getChildView(int groupPosition, int childPosition, boolean isLastChild, - View convertView, ViewGroup parent) { - AwesomeEntryViewHolder viewHolder = null; - - if (convertView == null) { - convertView = getInflater().inflate(R.layout.awesomebar_row, null); - - viewHolder = new AwesomeEntryViewHolder(); - viewHolder.titleView = (TextView) convertView.findViewById(R.id.title); - viewHolder.urlView = (TextView) convertView.findViewById(R.id.url); - viewHolder.faviconView = (FaviconView) convertView.findViewById(R.id.favicon); - viewHolder.bookmarkIconView = (ImageView) convertView.findViewById(R.id.bookmark_icon); - - convertView.setTag(viewHolder); - } else { - viewHolder = (AwesomeEntryViewHolder) convertView.getTag(); - } - - HistoryListAdapter adapter = getCursorAdapter(); - if (adapter == null) - return null; - - @SuppressWarnings("unchecked") - Map<String,Object> historyItem = - (Map<String,Object>) adapter.getChild(groupPosition, childPosition); - - String title = (String) historyItem.get(URLColumns.TITLE); - String url = (String) historyItem.get(URLColumns.URL); - - updateTitle(viewHolder.titleView, title, url); - updateUrl(viewHolder, url); - - byte[] b = (byte[]) historyItem.get(URLColumns.FAVICON); - Bitmap favicon = null; - - if (b != null) { - Bitmap bitmap = BitmapUtils.decodeByteArray(b); - if (bitmap != null) { - favicon = Favicons.getInstance().scaleImage(bitmap); - } - } - updateFavicon(viewHolder.faviconView, favicon, url); - - Integer bookmarkId = (Integer) historyItem.get(Combined.BOOKMARK_ID); - Integer display = (Integer) historyItem.get(Combined.DISPLAY); - - // The bookmark id will be 0 (null in database) when the url - // is not a bookmark. Reading list items are irrelevant in history - // tab. We should never show any sign or them. - int visibility = (bookmarkId != 0 && display != Combined.DISPLAY_READER ? - View.VISIBLE : View.GONE); - - viewHolder.bookmarkIconView.setVisibility(visibility); - viewHolder.bookmarkIconView.setImageResource(R.drawable.ic_awesomebar_star); - - return convertView; - } - } - - private static class GroupList extends LinkedList<Map<String,String>> { - private static final long serialVersionUID = 0L; - } - - private static class ChildrenList extends LinkedList<Map<String,Object>> { - private static final long serialVersionUID = 0L; - } - - private class HistoryQueryTask extends AsyncTask<Void, Void, Pair<GroupList,List<ChildrenList>>> { - private static final long MS_PER_DAY = 86400000; - private static final long MS_PER_WEEK = MS_PER_DAY * 7; - - @Override - protected Pair<GroupList,List<ChildrenList>> doInBackground(Void... arg0) { - Cursor cursor = BrowserDB.getRecentHistory(getContentResolver(), MAX_RESULTS); - - Date now = new Date(); - now.setHours(0); - now.setMinutes(0); - now.setSeconds(0); - - long today = now.getTime(); - - // Split the list of urls into separate date range groups - // and show it in an expandable list view. - List<ChildrenList> childrenLists = new LinkedList<ChildrenList>(); - ChildrenList children = null; - GroupList groups = new GroupList(); - HistorySection section = null; - - // Move cursor before the first row in preparation - // for the iteration. - cursor.moveToPosition(-1); - - // Split the history query results into adapters per time - // section (today, yesterday, week, older). Queries on content - // Browser content provider don't support limitting the number - // of returned rows so we limit it here. - while (cursor.moveToNext()) { - long time = cursor.getLong(cursor.getColumnIndexOrThrow(URLColumns.DATE_LAST_VISITED)); - HistorySection itemSection = getSectionForTime(time, today); - - if (section != itemSection) { - if (section != null) { - groups.add(createGroupItem(section)); - childrenLists.add(children); - } - - section = itemSection; - children = new ChildrenList(); - } - - children.add(createHistoryItem(cursor)); - } - - // Add any remaining section to the list if it hasn't - // been added to the list after the loop. - if (section != null && children != null) { - groups.add(createGroupItem(section)); - childrenLists.add(children); - } - - // Close the query cursor as we won't use it anymore - cursor.close(); - - // groups and childrenLists will be empty lists if there's no history - return Pair.<GroupList,List<ChildrenList>>create(groups, childrenLists); - } - - public Map<String,Object> createHistoryItem(Cursor cursor) { - Map<String,Object> historyItem = new HashMap<String,Object>(); - - String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)); - String title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE)); - byte[] favicon = cursor.getBlob(cursor.getColumnIndexOrThrow(URLColumns.FAVICON)); - Integer bookmarkId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID)); - Integer historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID)); - Integer display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY)); - - // Use the URL instead of an empty title for consistency with the normal URL - // bar view - this is the equivalent of getDisplayTitle() in Tab.java - if (title == null || title.length() == 0) - title = url; - - historyItem.put(URLColumns.URL, url); - historyItem.put(URLColumns.TITLE, title); - - if (favicon != null) - historyItem.put(URLColumns.FAVICON, favicon); - - historyItem.put(Combined.BOOKMARK_ID, bookmarkId); - historyItem.put(Combined.HISTORY_ID, historyId); - historyItem.put(Combined.DISPLAY, display); - - return historyItem; - } - - public Map<String,String> createGroupItem(HistorySection section) { - Map<String,String> groupItem = new HashMap<String,String>(); - - groupItem.put(URLColumns.TITLE, getSectionName(section)); - - return groupItem; - } - - private String getSectionName(HistorySection section) { - Resources resources = mContext.getResources(); - - switch (section) { - case TODAY: - return resources.getString(R.string.history_today_section); - case YESTERDAY: - return resources.getString(R.string.history_yesterday_section); - case WEEK: - return resources.getString(R.string.history_week_section); - case OLDER: - return resources.getString(R.string.history_older_section); - } - - return null; - } - - private void expandAllGroups(ExpandableListView historyList) { - int groupCount = mCursorAdapter.getGroupCount(); - - for (int i = 0; i < groupCount; i++) { - historyList.expandGroup(i); - } - } - - private HistorySection getSectionForTime(long time, long today) { - long delta = today - time; - - if (delta < 0) { - return HistorySection.TODAY; - } - - if (delta < MS_PER_DAY) { - return HistorySection.YESTERDAY; - } - - if (delta < MS_PER_WEEK) { - return HistorySection.WEEK; - } - - return HistorySection.OLDER; - } - - @Override - protected void onPostExecute(Pair<GroupList,List<ChildrenList>> result) { - mCursorAdapter = new HistoryListAdapter( - mContext, - result.first, - R.layout.awesomebar_header_row, - new String[] { URLColumns.TITLE }, - new int[] { R.id.title }, - result.second - ); - - if (mContentObserver == null) { - // Register an observer to update the history tab contents if they change. - mContentObserver = new ContentObserver(ThreadUtils.getBackgroundHandler()) { - @Override - public void onChange(boolean selfChange) { - mQueryTask = new HistoryQueryTask(); - mQueryTask.execute(); - } - }; - BrowserDB.registerHistoryObserver(getContentResolver(), mContentObserver); - } - - final ExpandableListView historyList = (ExpandableListView)getView(); - - // Hack: force this to the main thread, even though it should already be on it - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - historyList.setAdapter(mCursorAdapter); - expandAllGroups(historyList); - } - }); - - mQueryTask = null; - } - } - - public boolean handleItemClick(int groupPosition, int childPosition) { - HistoryListAdapter adapter = getCursorAdapter(); - if (adapter == null) - return false; - - @SuppressWarnings("unchecked") - Map<String,Object> historyItem = (Map<String,Object>) adapter.getChild(groupPosition, childPosition); - - String url = (String) historyItem.get(URLColumns.URL); - String title = (String) historyItem.get(URLColumns.TITLE); - sendToListener(url, title); - - return true; - } - - @Override - public ContextMenuSubject getSubject(ContextMenu menu, View view, ContextMenuInfo menuInfo) { - ContextMenuSubject subject = null; - - if (!(menuInfo instanceof ExpandableListView.ExpandableListContextMenuInfo)) { - Log.e(LOGTAG, "menuInfo is not ExpandableListContextMenuInfo"); - return subject; - } - - ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; - int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition); - int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); - - // Check if long tap is on a header row - if (groupPosition < 0 || childPosition < 0) - return subject; - - ExpandableListView exList = (ExpandableListView) view; - - // The history list is backed by a SimpleExpandableListAdapter - @SuppressWarnings("rawtypes") - Map map = (Map) exList.getExpandableListAdapter().getChild(groupPosition, childPosition); - subject = new AwesomeBar.ContextMenuSubject((Integer) map.get(Combined.HISTORY_ID), - (String) map.get(URLColumns.URL), - (byte[]) map.get(URLColumns.FAVICON), - (String) map.get(URLColumns.TITLE), - null); - - setupMenu(menu, subject); - - menu.findItem(R.id.remove_bookmark).setVisible(false); - menu.findItem(R.id.edit_bookmark).setVisible(false); - menu.findItem(R.id.open_in_reader).setVisible(false); - - return subject; - } -}
--- a/mobile/android/base/db/BrowserDB.java +++ b/mobile/android/base/db/BrowserDB.java @@ -82,16 +82,18 @@ public class BrowserDB { public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword); public void addReadingListItem(ContentResolver cr, String title, String uri); public void removeReadingListItemWithURL(ContentResolver cr, String uri); public Bitmap getFaviconForUrl(ContentResolver cr, String uri); + public byte[] getFaviconBytesForUrl(ContentResolver cr, String uri); + public Cursor getFaviconsForUrls(ContentResolver cr, List<String> urls); public String getFaviconUrlForHistoryUrl(ContentResolver cr, String url); public void updateFaviconForUrl(ContentResolver cr, String pageUri, Bitmap favicon, String faviconUri); public void updateThumbnailForUrl(ContentResolver cr, String uri, BitmapDrawable thumbnail); @@ -232,16 +234,20 @@ public class BrowserDB { public static void removeReadingListItemWithURL(ContentResolver cr, String uri) { sDb.removeReadingListItemWithURL(cr, uri); } public static Bitmap getFaviconForUrl(ContentResolver cr, String uri) { return sDb.getFaviconForUrl(cr, uri); } + public static byte[] getFaviconBytesForUrl(ContentResolver cr, String uri) { + return sDb.getFaviconBytesForUrl(cr, uri); + } + public static Cursor getFaviconsForUrls(ContentResolver cr, List<String> urls) { return sDb.getFaviconsForUrls(cr, urls); } public static String getFaviconUrlForHistoryUrl(ContentResolver cr, String url) { return sDb.getFaviconUrlForHistoryUrl(cr, url); }
--- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -17,16 +17,17 @@ import org.mozilla.gecko.db.BrowserContr import org.mozilla.gecko.gfx.BitmapUtils; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentValues; import android.database.ContentObserver; import android.database.Cursor; import android.database.CursorWrapper; +import android.database.DatabaseUtils; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.provider.Browser; import android.text.TextUtils; import android.util.Log; import java.io.ByteArrayOutputStream; @@ -47,17 +48,16 @@ public class LocalBrowserDB implements B private final String mProfile; // Map of folder GUIDs to IDs. Used for caching. private HashMap<String, Long> mFolderIdMap; // Use wrapped Boolean so that we can have a null state private Boolean mDesktopBookmarksExist; - private Boolean mReadingListItemsExist; private final Uri mBookmarksUriWithProfile; private final Uri mParentsUriWithProfile; private final Uri mHistoryUriWithProfile; private final Uri mHistoryExpireUriWithProfile; private final Uri mCombinedUriWithProfile; private final Uri mDeletedHistoryUriWithProfile; private final Uri mUpdateHistoryUriWithProfile; @@ -65,25 +65,22 @@ public class LocalBrowserDB implements B private final Uri mThumbnailsUriWithProfile; private static final String[] DEFAULT_BOOKMARK_COLUMNS = new String[] { Bookmarks._ID, Bookmarks.GUID, Bookmarks.URL, Bookmarks.TITLE, Bookmarks.TYPE, - Bookmarks.PARENT, - Bookmarks.KEYWORD, - Bookmarks.FAVICON }; + Bookmarks.PARENT }; public LocalBrowserDB(String profile) { mProfile = profile; mFolderIdMap = new HashMap<String, Long>(); mDesktopBookmarksExist = null; - mReadingListItemsExist = null; mBookmarksUriWithProfile = appendProfile(Bookmarks.CONTENT_URI); mParentsUriWithProfile = appendProfile(Bookmarks.PARENTS_CONTENT_URI); mHistoryUriWithProfile = appendProfile(History.CONTENT_URI); mHistoryExpireUriWithProfile = appendProfile(History.CONTENT_OLD_URI); mCombinedUriWithProfile = appendProfile(Combined.CONTENT_URI); mFaviconsUriWithProfile = appendProfile(Favicons.CONTENT_URI); mThumbnailsUriWithProfile = appendProfile(Thumbnails.CONTENT_URI); @@ -95,17 +92,16 @@ public class LocalBrowserDB implements B appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true"). appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(); } // Invalidate cached data @Override public void invalidateCachedState() { mDesktopBookmarksExist = null; - mReadingListItemsExist = null; } private Uri historyUriWithLimit(int limit) { return mHistoryUriWithProfile.buildUpon().appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit)).build(); } private Uri bookmarksUriWithLimit(int limit) { @@ -327,17 +323,16 @@ public class LocalBrowserDB implements B @Override public Cursor getRecentHistory(ContentResolver cr, int limit) { Cursor c = cr.query(combinedUriWithLimit(limit), new String[] { Combined._ID, Combined.BOOKMARK_ID, Combined.HISTORY_ID, Combined.URL, Combined.TITLE, - Combined.FAVICON, Combined.DISPLAY, Combined.DATE_LAST_VISITED, Combined.VISITS }, History.DATE_LAST_VISITED + " > 0", null, History.DATE_LAST_VISITED + " DESC"); return new LocalDBCursor(c); @@ -368,29 +363,24 @@ public class LocalBrowserDB implements B public void clearHistory(ContentResolver cr) { cr.delete(mHistoryUriWithProfile, null, null); } @Override public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) { Cursor c = null; boolean addDesktopFolder = false; - boolean addReadingListFolder = false; // We always want to show mobile bookmarks in the root view. if (folderId == Bookmarks.FIXED_ROOT_ID) { folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID); // We'll add a fake "Desktop Bookmarks" folder to the root view if desktop // bookmarks exist, so that the user can still access non-mobile bookmarks. addDesktopFolder = desktopBookmarksExist(cr); - - // We'll add the Reading List folder to the root view if any reading - // list items exist. - addReadingListFolder = readingListItemsExist(cr); } if (folderId == Bookmarks.FAKE_DESKTOP_FOLDER_ID) { // Since the "Desktop Bookmarks" folder doesn't actually exist, we // just fake it by querying specifically certain known desktop folders. c = cr.query(mBookmarksUriWithProfile, DEFAULT_BOOKMARK_COLUMNS, Bookmarks.GUID + " = ? OR " + @@ -408,19 +398,19 @@ public class LocalBrowserDB implements B Bookmarks.PARENT + " = ? AND " + "(" + Bookmarks.TYPE + " = ? OR " + Bookmarks.TYPE + " = ?)", new String[] { String.valueOf(folderId), String.valueOf(Bookmarks.TYPE_BOOKMARK), String.valueOf(Bookmarks.TYPE_FOLDER) }, null); } - if (addDesktopFolder || addReadingListFolder) { + if (addDesktopFolder) { // Wrap cursor to add fake desktop bookmarks and reading list folders - c = new SpecialFoldersCursorWrapper(c, addDesktopFolder, addReadingListFolder); + c = new SpecialFoldersCursorWrapper(c, addDesktopFolder); } return new LocalDBCursor(c); } // Returns true if any desktop bookmarks exist, which will be true if the user // has set up sync at one point, or done a profile migration from XUL fennec. private boolean desktopBookmarksExist(ContentResolver cr) { @@ -447,39 +437,16 @@ public class LocalBrowserDB implements B c.close(); } // Cache result for future queries mDesktopBookmarksExist = (count > 0); return mDesktopBookmarksExist; } - private boolean readingListItemsExist(ContentResolver cr) { - if (mReadingListItemsExist != null) - return mReadingListItemsExist; - - Cursor c = null; - int count = 0; - try { - c = cr.query(bookmarksUriWithLimit(1), - new String[] { Bookmarks._ID }, - Bookmarks.PARENT + " = ?", - new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) }, - null); - count = c.getCount(); - } finally { - if (c != null) - c.close(); - } - - // Cache result for future queries - mReadingListItemsExist = (count > 0); - return mReadingListItemsExist; - } - @Override public int getReadingListCount(ContentResolver cr) { // This method is about the Reading List, not normal bookmarks Cursor c = null; try { c = cr.query(mBookmarksUriWithProfile, new String[] { Bookmarks._ID }, Bookmarks.PARENT + " = ?", @@ -722,40 +689,49 @@ public class LocalBrowserDB implements B cr.update(mBookmarksUriWithProfile, values, Bookmarks._ID + " = ?", new String[] { String.valueOf(id) }); } @Override public Bitmap getFaviconForUrl(ContentResolver cr, String uri) { + final byte[] b = getFaviconBytesForUrl(cr, uri); + if (b == null) { + return null; + } + + return BitmapUtils.decodeByteArray(b); + } + + @Override + public byte[] getFaviconBytesForUrl(ContentResolver cr, String uri) { Cursor c = null; byte[] b = null; try { c = cr.query(mCombinedUriWithProfile, new String[] { Combined.FAVICON }, Combined.URL + " = ?", new String[] { uri }, null); - if (c.moveToFirst()) { - int faviconIndex = c.getColumnIndexOrThrow(Combined.FAVICON); - b = c.getBlob(faviconIndex); + if (!c.moveToFirst()) { + return null; } + + final int faviconIndex = c.getColumnIndexOrThrow(Combined.FAVICON); + b = c.getBlob(faviconIndex); } finally { - if (c != null) + if (c != null) { c.close(); + } } - if (b == null) { - return null; - } - - return BitmapUtils.decodeByteArray(b); + return b; } @Override public String getFaviconUrlForHistoryUrl(ContentResolver cr, String uri) { Cursor c = null; try { c = cr.query(mHistoryUriWithProfile, @@ -772,33 +748,33 @@ public class LocalBrowserDB implements B } return null; } @Override public Cursor getFaviconsForUrls(ContentResolver cr, List<String> urls) { StringBuilder selection = new StringBuilder(); - String[] selectionArgs = new String[urls.size()]; + selection.append(Favicons.URL + " IN ("); for (int i = 0; i < urls.size(); i++) { - final String url = urls.get(i); + final String url = urls.get(i); + + if (i > 0) + selection.append(", "); - if (i > 0) - selection.append(" OR "); + DatabaseUtils.appendEscapedSQLString(selection, url); + } - selection.append(Favicons.URL + " = ?"); - selectionArgs[i] = url; - } + selection.append(")"); return cr.query(mCombinedUriWithProfile, new String[] { Combined.URL, Combined.FAVICON }, selection.toString(), - selectionArgs, - null); + null, null); } @Override public void updateFaviconForUrl(ContentResolver cr, String pageUri, Bitmap favicon, String faviconUri) { ContentValues values = new ContentValues(); values.put(Favicons.URL, faviconUri); values.put(Favicons.PAGE_URL, pageUri); @@ -1068,96 +1044,78 @@ public class LocalBrowserDB implements B } // This wrapper adds a fake "Desktop Bookmarks" folder entry to the // beginning of the cursor's data set. private class SpecialFoldersCursorWrapper extends CursorWrapper { private int mIndexOffset; private int mDesktopBookmarksIndex = -1; - private int mReadingListIndex = -1; private boolean mAtDesktopBookmarksPosition = false; - private boolean mAtReadingListPosition = false; - public SpecialFoldersCursorWrapper(Cursor c, boolean showDesktopBookmarks, boolean showReadingList) { + public SpecialFoldersCursorWrapper(Cursor c, boolean showDesktopBookmarks) { super(c); mIndexOffset = 0; if (showDesktopBookmarks) { mDesktopBookmarksIndex = mIndexOffset; mIndexOffset++; } - - if (showReadingList) { - mReadingListIndex = mIndexOffset; - mIndexOffset++; - } } @Override public int getCount() { return super.getCount() + mIndexOffset; } @Override public boolean moveToPosition(int position) { mAtDesktopBookmarksPosition = (mDesktopBookmarksIndex == position); - mAtReadingListPosition = (mReadingListIndex == position); - if (mAtDesktopBookmarksPosition || mAtReadingListPosition) + if (mAtDesktopBookmarksPosition) return true; return super.moveToPosition(position - mIndexOffset); } @Override public long getLong(int columnIndex) { - if (!mAtDesktopBookmarksPosition && !mAtReadingListPosition) + if (!mAtDesktopBookmarksPosition) return super.getLong(columnIndex); if (columnIndex == getColumnIndex(Bookmarks.PARENT)) { return Bookmarks.FIXED_ROOT_ID; } return -1; } @Override public int getInt(int columnIndex) { - if (!mAtDesktopBookmarksPosition && !mAtReadingListPosition) + if (!mAtDesktopBookmarksPosition) return super.getInt(columnIndex); - if (columnIndex == getColumnIndex(Bookmarks._ID)) { - if (mAtDesktopBookmarksPosition) { + if (columnIndex == getColumnIndex(Bookmarks._ID) && mAtDesktopBookmarksPosition) return Bookmarks.FAKE_DESKTOP_FOLDER_ID; - } else if (mAtReadingListPosition) { - return Bookmarks.FIXED_READING_LIST_ID; - } - } if (columnIndex == getColumnIndex(Bookmarks.TYPE)) return Bookmarks.TYPE_FOLDER; return -1; } @Override public String getString(int columnIndex) { - if (!mAtDesktopBookmarksPosition && !mAtReadingListPosition) + if (!mAtDesktopBookmarksPosition) return super.getString(columnIndex); - if (columnIndex == getColumnIndex(Bookmarks.GUID)) { - if (mAtDesktopBookmarksPosition) { + if (columnIndex == getColumnIndex(Bookmarks.GUID) && mAtDesktopBookmarksPosition) return Bookmarks.FAKE_DESKTOP_FOLDER_GUID; - } else if (mAtReadingListPosition) { - return Bookmarks.READING_LIST_FOLDER_GUID; - } - } return ""; } } private static class LocalDBCursor extends CursorWrapper { public LocalDBCursor(Cursor c) { super(c);
--- a/mobile/android/base/gfx/LayerView.java +++ b/mobile/android/base/gfx/LayerView.java @@ -260,25 +260,25 @@ public class LayerView extends FrameLayo if (shouldUseTextureView()) { mTextureView = new TextureView(getContext()); mTextureView.setSurfaceTextureListener(new SurfaceTextureListener()); // The background is set to this color when the LayerView is // created, and it will be shown immediately at startup. Shortly // after, the tab's background color will be used before any content // is shown. - mTextureView.setBackgroundResource(R.color.background_normal); + mTextureView.setBackgroundColor(Color.WHITE); addView(mTextureView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); } else { // This will stop PropertyAnimator from creating a drawing cache (i.e. a bitmap) // from a SurfaceView, which is just not possible (the bitmap will be transparent). setWillNotCacheDrawing(false); mSurfaceView = new LayerSurfaceView(getContext(), this); - mSurfaceView.setBackgroundResource(R.color.background_normal); + mSurfaceView.setBackgroundColor(Color.WHITE); addView(mSurfaceView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); SurfaceHolder holder = mSurfaceView.getHolder(); holder.addCallback(new SurfaceListener()); holder.setFormat(PixelFormat.RGB_565); } }
new file mode 100644 --- /dev/null +++ b/mobile/android/base/home/BookmarkFolderView.java @@ -0,0 +1,55 @@ +/* -*- 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.R; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextView; + +public class BookmarkFolderView extends TextView { + private static final int[] STATE_OPEN = { R.attr.state_open }; + + private boolean mIsOpen = false; + + public BookmarkFolderView(Context context) { + super(context); + } + + public BookmarkFolderView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public BookmarkFolderView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public int[] onCreateDrawableState(int extraSpace) { + final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); + + if (mIsOpen) { + mergeDrawableStates(drawableState, STATE_OPEN); + } + + return drawableState; + } + + public void open() { + if (!mIsOpen) { + mIsOpen = true; + refreshDrawableState(); + } + } + + public void close() { + if (mIsOpen) { + mIsOpen = false; + refreshDrawableState(); + } + } +}
new file mode 100644 --- /dev/null +++ b/mobile/android/base/home/BookmarkThumbnailView.java @@ -0,0 +1,102 @@ +/* -*- 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.R; +import org.mozilla.gecko.ThumbnailHelper; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + * A height constrained ImageView to show thumbnails of top bookmarks. + */ +public class BookmarkThumbnailView extends ImageView { + private static final String LOGTAG = "GeckoBookmarkThumbnailView"; + + // 27.34% opacity filter for the dominant color. + private static final int COLOR_FILTER = 0x46FFFFFF; + + // Default filter color for "Add a bookmark" views. + private static final int DEFAULT_COLOR = 0x46ECF0F3; + + // Stroke width for the border. + private final float mStrokeWidth = getResources().getDisplayMetrics().density * 2; + + // Paint for drawing the border. + private static Paint sBorderPaint; + + // Initializing the static border paint. + static { + sBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + sBorderPaint.setColor(0xFFCFD9E1); + sBorderPaint.setStyle(Paint.Style.STROKE); + } + + public BookmarkThumbnailView(Context context) { + this(context, null); + + // A border will be drawn if needed. + setWillNotDraw(false); + } + + public BookmarkThumbnailView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.bookmarkThumbnailViewStyle); + } + + public BookmarkThumbnailView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Measure the view to determine the measured width and height. + * The height is constrained by the measured width. + * + * @param widthMeasureSpec horizontal space requirements as imposed by the parent. + * @param heightMeasureSpec vertical space requirements as imposed by the parent, but ignored. + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Default measuring. + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // Force the height based on the aspect ratio. + final int width = getMeasuredWidth(); + final int height = (int) (width * ThumbnailHelper.THUMBNAIL_ASPECT_RATIO); + setMeasuredDimension(width, height); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (getBackground() == null) { + sBorderPaint.setStrokeWidth(mStrokeWidth); + canvas.drawRect(0, 0, getWidth(), getHeight(), sBorderPaint); + } + } + + /** + * Sets the background to a Drawable by applying the specified color as a filter. + * + * @param color the color filter to apply over the drawable. + */ + @Override + public void setBackgroundColor(int color) { + int colorFilter = color == 0 ? DEFAULT_COLOR : color & COLOR_FILTER; + Drawable drawable = getResources().getDrawable(R.drawable.bookmark_thumbnail_bg); + drawable.setColorFilter(colorFilter, Mode.SRC_ATOP); + setBackgroundDrawable(drawable); + } +}
new file mode 100644 --- /dev/null +++ b/mobile/android/base/home/BookmarksListAdapter.java @@ -0,0 +1,190 @@ +/* -*- 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.R; +import org.mozilla.gecko.db.BrowserContract.Bookmarks; + +import android.content.Context; +import android.content.res.Resources; +import android.database.Cursor; +import android.util.Pair; +import android.view.View; + +import java.util.LinkedList; + +/** + * Adapter to back the BookmarksListView with a list of bookmarks. + */ +class BookmarksListAdapter extends MultiTypeCursorAdapter { + private static final int VIEW_TYPE_ITEM = 0; + private static final int VIEW_TYPE_FOLDER = 1; + + private static final int[] VIEW_TYPES = new int[] { VIEW_TYPE_ITEM, VIEW_TYPE_FOLDER }; + private static final int[] LAYOUT_TYPES = new int[] { R.layout.bookmark_item_row, R.layout.bookmark_folder_row }; + + // A listener that knows how to refresh the list for a given folder id. + // This is usually implemented by the enclosing fragment/activity. + public static interface OnRefreshFolderListener { + // The folder id to refresh the list with. + public void onRefreshFolder(int folderId); + } + + // mParentStack holds folder id/title pairs that allow us to navigate + // back up the folder heirarchy. + private LinkedList<Pair<Integer, String>> mParentStack; + + // Refresh folder listener. + private OnRefreshFolderListener mListener; + + public BookmarksListAdapter(Context context, Cursor cursor) { + // Initializing with a null cursor. + super(context, cursor, VIEW_TYPES, LAYOUT_TYPES); + + mParentStack = new LinkedList<Pair<Integer, String>>(); + + // Add the root folder to the stack + Pair<Integer, String> rootFolder = new Pair<Integer, String>(Bookmarks.FIXED_ROOT_ID, ""); + mParentStack.addFirst(rootFolder); + } + + // Refresh the current folder by executing a new task. + private void refreshCurrentFolder() { + if (mListener != null) { + mListener.onRefreshFolder(mParentStack.peek().first); + } + } + + /** + * Moves to parent folder, if one exists. + */ + public void moveToParentFolder() { + // If we're already at the root, we can't move to a parent folder + if (mParentStack.size() != 1) { + mParentStack.removeFirst(); + refreshCurrentFolder(); + } + } + + /** + * Moves to child folder, given a folderId. + * + * @param folderId The id of the folder to show. + * @param folderTitle The title of the folder to show. + */ + public void moveToChildFolder(int folderId, String folderTitle) { + Pair<Integer, String> folderPair = new Pair<Integer, String>(folderId, folderTitle); + mParentStack.addFirst(folderPair); + refreshCurrentFolder(); + } + + /** + * Set a listener that can refresh this adapter. + * + * @param listener The listener that can refresh the adapter. + */ + public void setOnRefreshFolderListener(OnRefreshFolderListener listener) { + mListener = listener; + } + + @Override + public int getItemViewType(int position) { + // The position also reflects the opened child folder row. + if (isShowingChildFolder()) { + if (position == 0) { + return VIEW_TYPE_FOLDER; + } + + // Accounting for the folder view. + position--; + } + + final Cursor c = getCursor(position); + if (c.getInt(c.getColumnIndexOrThrow(Bookmarks.TYPE)) == Bookmarks.TYPE_FOLDER) { + return VIEW_TYPE_FOLDER; + } + + // Default to returning normal item type. + return VIEW_TYPE_ITEM; + } + + /** + * Get the title of the folder given a cursor moved to the position. + * + * @param context The context of the view. + * @param cursor A cursor moved to the required position. + * @return The title of the folder at the position. + */ + public String getFolderTitle(Context context, Cursor c) { + String guid = c.getString(c.getColumnIndexOrThrow(Bookmarks.GUID)); + + // If we don't have a special GUID, just return the folder title from the DB. + if (guid == null || guid.length() == 12) { + return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE)); + } + + Resources res = context.getResources(); + + // Use localized strings for special folder names. + if (guid.equals(Bookmarks.FAKE_DESKTOP_FOLDER_GUID)) { + return res.getString(R.string.bookmarks_folder_desktop); + } else if (guid.equals(Bookmarks.MENU_FOLDER_GUID)) { + return res.getString(R.string.bookmarks_folder_menu); + } else if (guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID)) { + return res.getString(R.string.bookmarks_folder_toolbar); + } else if (guid.equals(Bookmarks.UNFILED_FOLDER_GUID)) { + return res.getString(R.string.bookmarks_folder_unfiled); + } + + // If for some reason we have a folder with a special GUID, but it's not one of + // the special folders we expect in the UI, just return the title from the DB. + return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE)); + } + + /** + * @return true, if currently showing a child folder, false otherwise. + */ + public boolean isShowingChildFolder() { + return (mParentStack.peek().first != Bookmarks.FIXED_ROOT_ID); + } + + @Override + public int getCount() { + return super.getCount() + (isShowingChildFolder() ? 1 : 0); + } + + @Override + public void bindView(View view, Context context, int position) { + final int viewType = getItemViewType(position); + + final Cursor cursor; + if (isShowingChildFolder()) { + if (position == 0) { + cursor = null; + } else { + // Accounting for the folder view. + position--; + cursor = getCursor(position); + } + } else { + cursor = getCursor(position); + } + + if (viewType == VIEW_TYPE_ITEM) { + final TwoLinePageRow row = (TwoLinePageRow) view; + row.updateFromCursor(cursor); + } else { + final BookmarkFolderView row = (BookmarkFolderView) view; + if (cursor == null) { + row.setText(mParentStack.peek().second); + row.open(); + } else { + row.setText(getFolderTitle(context, cursor)); + row.close(); + } + } + } +}
new file mode 100644 --- /dev/null +++ b/mobile/android/base/home/BookmarksListView.java @@ -0,0 +1,144 @@ +/* -*- 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.R; +import org.mozilla.gecko.db.BrowserContract.Bookmarks; +import org.mozilla.gecko.db.BrowserDB.URLColumns; +import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; + +import android.content.Context; +import android.database.Cursor; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.widget.AdapterView; +import android.widget.HeaderViewListAdapter; +import android.widget.ListAdapter; +import android.widget.ListView; + +import java.util.EnumSet; + +/** + * A ListView of bookmarks. + */ +public class BookmarksListView extends HomeListView + implements AdapterView.OnItemClickListener{ + public static final String LOGTAG = "GeckoBookmarksListView"; + + // The last motion event that was intercepted. + private MotionEvent mMotionEvent; + + // The default touch slop. + private int mTouchSlop; + + public BookmarksListView(Context context) { + this(context, null); + } + + public BookmarksListView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.bookmarksListViewStyle); + } + + public BookmarksListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + // Scaled touch slop for this context. + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + setOnItemClickListener(this); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + switch(event.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: { + // Store the event by obtaining a copy. + mMotionEvent = MotionEvent.obtain(event); + break; + } + + case MotionEvent.ACTION_MOVE: { + if ((mMotionEvent != null) && + (Math.abs(event.getY() - mMotionEvent.getY()) > mTouchSlop)) { + // The user is scrolling. Pass the last event to this view, + // and make this view scroll. + onTouchEvent(mMotionEvent); + return true; + } + break; + } + + default: { + mMotionEvent = null; + break; + } + } + + // Do default interception. + return super.onInterceptTouchEvent(event); + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + final ListView list = (ListView) parent; + final int headerCount = list.getHeaderViewsCount(); + + if (position < headerCount) { + // The click is on a header, don't do anything. + return; + } + + // Absolute position for the adapter. + position -= headerCount; + + BookmarksListAdapter adapter; + ListAdapter listAdapter = getAdapter(); + if (listAdapter instanceof HeaderViewListAdapter) { + adapter = (BookmarksListAdapter) ((HeaderViewListAdapter) listAdapter).getWrappedAdapter(); + } else { + adapter = (BookmarksListAdapter) listAdapter; + } + + if (adapter.isShowingChildFolder()) { + if (position == 0) { + // If we tap on an opened folder, move back to parent folder. + adapter.moveToParentFolder(); + return; + } + + // Accounting for the folder view. + position--; + } + + final Cursor cursor = adapter.getCursor(); + if (cursor == null) { + return; + } + + cursor.moveToPosition(position); + + int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE)); + if (type == Bookmarks.TYPE_FOLDER) { + // If we're clicking on a folder, update adapter to move to that folder + final int folderId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID)); + final String folderTitle = adapter.getFolderTitle(parent.getContext(), cursor); + adapter.moveToChildFolder(folderId, folderTitle); + } else { + // Otherwise, just open the URL + final String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)); + + // This item is a TwoLinePageRow, so we allow switch-to-tab. + getOnUrlOpenListener().onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB)); + } + } +}
new file mode 100644 --- /dev/null +++ b/mobile/android/base/home/BookmarksPage.java @@ -0,0 +1,593 @@ +/* -*- 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.Favicons; +import org.mozilla.gecko.R; +import org.mozilla.gecko.Tabs; +import org.mozilla.gecko.db.BrowserContract.Bookmarks; +import org.mozilla.gecko.db.BrowserContract.Thumbnails; +import org.mozilla.gecko.db.BrowserDB; +import org.mozilla.gecko.db.BrowserDB.URLColumns; +import org.mozilla.gecko.gfx.BitmapUtils; +import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener; +import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo; +import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; +import org.mozilla.gecko.home.PinBookmarkDialog.OnBookmarkSelectedListener; +import org.mozilla.gecko.home.TopBookmarksAdapter.Thumbnail; +import org.mozilla.gecko.home.TopBookmarksView.OnPinBookmarkListener; +import org.mozilla.gecko.home.TopBookmarksView.TopBookmarksContextMenuInfo; +import org.mozilla.gecko.util.ThreadUtils; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Configuration; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.LoaderManager; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.AsyncTaskLoader; +import android.support.v4.content.Loader; +import android.text.TextUtils; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * A page in about:home that displays a ListView of bookmarks. + */ +public class BookmarksPage extends HomeFragment { + public static final String LOGTAG = "GeckoBookmarksPage"; + + // Cursor loader ID for list of bookmarks. + private static final int LOADER_ID_BOOKMARKS_LIST = 0; + + // Cursor loader ID for grid of bookmarks. + private static final int LOADER_ID_TOP_BOOKMARKS = 1; + + // Loader ID for thumbnails. + private static final int LOADER_ID_THUMBNAILS = 2; + + // Key for bookmarks folder id. + private static final String BOOKMARKS_FOLDER_KEY = "folder_id"; + + // Key for thumbnail urls. + private static final String THUMBNAILS_URLS_KEY = "urls"; + + // List of bookmarks. + private BookmarksListView mList; + + // Grid of top bookmarks. + private TopBookmarksView mTopBookmarks; + + // Adapter for list of bookmarks. + private BookmarksListAdapter mListAdapter; + + // Adapter for grid of bookmarks. + private TopBookmarksAdapter mTopBookmarksAdapter; + + // Callback for cursor loaders. + private CursorLoaderCallbacks mLoaderCallbacks; + + // Callback for thumbnail loader. + private ThumbnailsLoaderCallbacks mThumbnailsLoaderCallbacks; + + // Listener for pinning bookmarks. + private PinBookmarkListener mPinBookmarkListener; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + BookmarksListView list = (BookmarksListView) inflater.inflate(R.layout.home_bookmarks_page, container, false); + + mTopBookmarks = new TopBookmarksView(getActivity()); + list.addHeaderView(mTopBookmarks); + + return list; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + OnUrlOpenListener listener = null; + try { + listener = (OnUrlOpenListener) getActivity(); + } catch (ClassCastException e) { + throw new ClassCastException(getActivity().toString() + + " must implement HomePager.OnUrlOpenListener"); + } + + mPinBookmarkListener = new PinBookmarkListener(); + + mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list); + mList.setTag(HomePager.LIST_TAG_BOOKMARKS); + mList.setOnUrlOpenListener(listener); + + mTopBookmarks.setOnUrlOpenListener(listener); + mTopBookmarks.setOnPinBookmarkListener(mPinBookmarkListener); + + registerForContextMenu(mList); + registerForContextMenu(mTopBookmarks); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + final Activity activity = getActivity(); + + // Setup the top bookmarks adapter. + mTopBookmarksAdapter = new TopBookmarksAdapter(activity, null); + mTopBookmarks.setAdapter(mTopBookmarksAdapter); + + // Setup the list adapter. + mListAdapter = new BookmarksListAdapter(activity, null); + mListAdapter.setOnRefreshFolderListener(new OnRefreshFolderListener() { + @Override + public void onRefreshFolder(int folderId) { + // Restart the loader with folder as the argument. + Bundle bundle = new Bundle(); + bundle.putInt(BOOKMARKS_FOLDER_KEY, folderId); + getLoaderManager().restartLoader(LOADER_ID_BOOKMARKS_LIST, bundle, mLoaderCallbacks); + } + }); + mList.setAdapter(mListAdapter); + + // Create callbacks before the initial loader is started. + mLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager()); + mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks(); + loadIfVisible(); + } + + @Override + public void onDestroyView() { + mList = null; + mListAdapter = null; + mTopBookmarks = null; + mTopBookmarksAdapter = null; + mPinBookmarkListener = null; + super.onDestroyView(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // Reattach the fragment, forcing a reinflation of its view. + // We use commitAllowingStateLoss() instead of commit() here to avoid + // an IllegalStateException. If the phone is rotated while Fennec + // is in the background, onConfigurationChanged() is fired. + // onConfigurationChanged() is called before onResume(), so + // using commit() would throw an IllegalStateException since it can't + // be used between the Activity's onSaveInstanceState() and + // onResume(). + if (isVisible()) { + getFragmentManager().beginTransaction() + .detach(this) + .attach(this) + .commitAllowingStateLoss(); + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { + if (menuInfo == null) { + return; + } + + // HomeFragment will handle the default case. + if (menuInfo instanceof HomeContextMenuInfo) { + super.onCreateContextMenu(menu, view, menuInfo); + } + + if (!(menuInfo instanceof TopBookmarksContextMenuInfo)) { + return; + } + + MenuInflater inflater = new MenuInflater(view.getContext()); + inflater.inflate(R.menu.top_bookmarks_contextmenu, menu); + + TopBookmarksContextMenuInfo info = (TopBookmarksContextMenuInfo) menuInfo; + + if (!TextUtils.isEmpty(info.url)) { + if (info.isPinned) { + menu.findItem(R.id.top_bookmarks_pin).setVisible(false); + } else { + menu.findItem(R.id.top_bookmarks_unpin).setVisible(false); + } + } else { + menu.findItem(R.id.top_bookmarks_open_new_tab).setVisible(false); + menu.findItem(R.id.top_bookmarks_open_private_tab).setVisible(false); + menu.findItem(R.id.top_bookmarks_pin).setVisible(false); + menu.findItem(R.id.top_bookmarks_unpin).setVisible(false); + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + ContextMenuInfo menuInfo = item.getMenuInfo(); + + // HomeFragment will handle the default case. + if (menuInfo == null || !(menuInfo instanceof TopBookmarksContextMenuInfo)) { + return false; + } + + TopBookmarksContextMenuInfo info = (TopBookmarksContextMenuInfo) menuInfo; + final Activity activity = getActivity(); + + final int itemId = item.getItemId(); + if (itemId == R.id.top_bookmarks_open_new_tab || itemId == R.id.top_bookmarks_open_private_tab) { + if (info.url == null) { + Log.e(LOGTAG, "Can't open in new tab because URL is null"); + return false; + } + + int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND; + if (item.getItemId() == R.id.top_bookmarks_open_private_tab) + flags |= Tabs.LOADURL_PRIVATE; + + Tabs.getInstance().loadUrl(info.url, flags); + Toast.makeText(activity, R.string.new_tab_opened, Toast.LENGTH_SHORT).show(); + return true; + } + + if (itemId == R.id.top_bookmarks_pin) { + final String url = info.url; + final String title = info.title; + final int position = info.position; + final Context context = getActivity().getApplicationContext(); + + ThreadUtils.postToBackgroundThread(new Runnable() { + @Override + public void run() { + BrowserDB.pinSite(context.getContentResolver(), url, title, position); + } + }); + + return true; + } + + if (itemId == R.id.top_bookmarks_unpin) { + final int position = info.position; + final Context context = getActivity().getApplicationContext(); + + ThreadUtils.postToBackgroundThread(new Runnable() { + @Override + public void run() { + BrowserDB.unpinSite(context.getContentResolver(), position); + } + }); + + return true; + } + + if (itemId == R.id.top_bookmarks_edit) { + mPinBookmarkListener.onPinBookmark(info.position);