Merge latest green fx-team changeset and mozilla-central
|
@ -82,6 +82,13 @@ public interface Actions {
|
|||
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -22,7 +22,9 @@ 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;
|
||||
|
||||
|
@ -458,6 +460,41 @@ public class FennecNativeActions implements Actions {
|
|||
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);
|
||||
|
|
|
@ -36,7 +36,6 @@ public class ActivityHandlerHelper implements GeckoEventListener {
|
|||
|
||||
private final ActivityResultHandlerMap mActivityResultHandlerMap;
|
||||
private final FilePickerResultHandlerSync mFilePickerResultHandlerSync;
|
||||
private final AwesomebarResultHandler mAwesomebarResultHandler;
|
||||
private final CameraImageResultHandler mCameraImageResultHandler;
|
||||
private final CameraVideoResultHandler mCameraVideoResultHandler;
|
||||
|
||||
|
@ -58,7 +57,6 @@ public class ActivityHandlerHelper implements GeckoEventListener {
|
|||
};
|
||||
mActivityResultHandlerMap = new ActivityResultHandlerMap();
|
||||
mFilePickerResultHandlerSync = new FilePickerResultHandlerSync(mFilePickerResult);
|
||||
mAwesomebarResultHandler = new AwesomebarResultHandler();
|
||||
mCameraImageResultHandler = new CameraImageResultHandler(mFilePickerResult);
|
||||
mCameraVideoResultHandler = new CameraVideoResultHandler(mFilePickerResult);
|
||||
GeckoAppShell.getEventDispatcher().registerEventListener("FilePicker:Show", this);
|
||||
|
@ -91,10 +89,6 @@ public class ActivityHandlerHelper implements GeckoEventListener {
|
|||
}
|
||||
}
|
||||
|
||||
public int makeRequestCodeForAwesomebar() {
|
||||
return mActivityResultHandlerMap.put(mAwesomebarResultHandler);
|
||||
}
|
||||
|
||||
public int makeRequestCode(ActivityResultHandler aHandler) {
|
||||
return mActivityResultHandlerMap.put(aHandler);
|
||||
}
|
||||
|
|
|
@ -230,11 +230,6 @@
|
|||
</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"
|
||||
|
|
|
@ -17,8 +17,12 @@ public class AnimatedHeightLayout extends RelativeLayout {
|
|||
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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,7 +73,7 @@ public class BackButton extends ShapedButton {
|
|||
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);
|
||||
|
@ -95,6 +95,6 @@ public class BackButton extends ShapedButton {
|
|||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
setBackgroundResource(R.drawable.address_bar_nav_button);
|
||||
setBackgroundResource(R.drawable.url_bar_nav_button);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,9 @@ 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;
|
||||
|
@ -21,7 +24,6 @@ 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;
|
||||
|
||||
|
@ -49,6 +51,8 @@ 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;
|
||||
|
@ -70,6 +74,7 @@ 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
|
||||
|
@ -77,8 +82,10 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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";
|
||||
|
@ -96,8 +103,13 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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
|
||||
|
@ -152,10 +164,6 @@ abstract public class BrowserApp extends GeckoApp
|
|||
|
||||
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;
|
||||
|
@ -182,14 +190,14 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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)
|
||||
|
@ -242,12 +250,6 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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
|
||||
|
@ -263,7 +265,7 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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();
|
||||
|
@ -330,7 +332,11 @@ abstract public class BrowserApp extends GeckoApp
|
|||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
@ -383,19 +389,6 @@ abstract public class BrowserApp extends GeckoApp
|
|||
});
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
@ -427,29 +420,50 @@ abstract public class BrowserApp extends GeckoApp
|
|||
// 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();
|
||||
} else {
|
||||
if (mHomePager.isVisible()) {
|
||||
mLayerView.requestFocus();
|
||||
} else {
|
||||
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);
|
||||
|
||||
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);
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
|
@ -468,6 +482,7 @@ abstract public class BrowserApp extends GeckoApp
|
|||
registerEventListener("Telemetry:Gather");
|
||||
registerEventListener("Settings:Show");
|
||||
registerEventListener("Updater:Launch");
|
||||
registerEventListener("Reader:GoToReadingList");
|
||||
|
||||
Distribution.init(this, getPackageResourcePath());
|
||||
JavaAddonManager.getInstance().init(getApplicationContext());
|
||||
|
@ -493,7 +508,7 @@ abstract public class BrowserApp extends GeckoApp
|
|||
|
||||
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
|
||||
|
@ -526,6 +541,25 @@ abstract public class BrowserApp extends GeckoApp
|
|||
});
|
||||
}
|
||||
|
||||
@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();
|
||||
|
@ -539,8 +573,6 @@ abstract public class BrowserApp extends GeckoApp
|
|||
registerEventListener("Prompt:ShowTop");
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void showBookmarkDialog() {
|
||||
final Tab tab = Tabs.getInstance().getSelectedTab();
|
||||
final Prompt ps = new Prompt(this, new Prompt.PromptCallback() {
|
||||
|
@ -583,14 +615,14 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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);
|
||||
}
|
||||
|
@ -609,7 +641,8 @@ abstract public class BrowserApp extends GeckoApp
|
|||
|
||||
@Override
|
||||
public boolean onSearchRequested() {
|
||||
return showAwesomebar(AwesomeBar.Target.CURRENT_TAB);
|
||||
enterEditingMode();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -631,7 +664,7 @@ abstract public class BrowserApp extends GeckoApp
|
|||
if (itemId == R.id.paste) {
|
||||
String text = Clipboard.getText();
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
showAwesomebar(AwesomeBar.Target.CURRENT_TAB, text);
|
||||
enterEditingMode(text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -690,41 +723,6 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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) {
|
||||
|
@ -776,6 +774,7 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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);
|
||||
|
@ -843,7 +842,7 @@ abstract public class BrowserApp extends GeckoApp
|
|||
|
||||
@Override
|
||||
public void onMetricsChanged(ImmutableViewportMetrics aMetrics) {
|
||||
if (mAboutHome.getUserVisibleHint() || mBrowserToolbar == null) {
|
||||
if (mHomePager.isVisible() || mBrowserToolbar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -878,7 +877,7 @@ abstract public class BrowserApp extends GeckoApp
|
|||
|
||||
@Override
|
||||
public void onPanZoomStopped() {
|
||||
if (!isDynamicToolbarEnabled() || mAboutHome.getUserVisibleHint()) {
|
||||
if (!isDynamicToolbarEnabled() || mHomePager.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -900,12 +899,19 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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);
|
||||
}
|
||||
|
@ -957,37 +963,11 @@ abstract public class BrowserApp extends GeckoApp
|
|||
}
|
||||
}
|
||||
|
||||
@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
|
||||
|
@ -1155,6 +1135,8 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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();
|
||||
|
@ -1171,7 +1153,7 @@ abstract public class BrowserApp extends GeckoApp
|
|||
|
||||
@Override
|
||||
public void addTab() {
|
||||
showAwesomebar(AwesomeBar.Target.NEW_TAB);
|
||||
Tabs.getInstance().loadUrl("about:home", Tabs.LOADURL_NEW_TAB);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1234,7 +1216,7 @@ abstract public class BrowserApp extends GeckoApp
|
|||
}
|
||||
|
||||
mMainLayoutAnimator = new PropertyAnimator(animationLength, sTabsInterpolator);
|
||||
mMainLayoutAnimator.setPropertyAnimationListener(this);
|
||||
mMainLayoutAnimator.addPropertyAnimationListener(this);
|
||||
|
||||
if (hasTabsSideBar()) {
|
||||
mMainLayoutAnimator.attach(mMainLayout,
|
||||
|
@ -1284,7 +1266,66 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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 */
|
||||
|
@ -1330,14 +1371,80 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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);
|
||||
}
|
||||
|
||||
private void showAboutHome() {
|
||||
if (mAboutHome.getUserVisibleHint()) {
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1350,29 +1457,29 @@ abstract public class BrowserApp extends GeckoApp
|
|||
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);
|
||||
|
||||
mBrowserToolbar.setNextFocusDownId(R.id.abouthome_content);
|
||||
mHomePager.show(getSupportFragmentManager(), page, animator);
|
||||
}
|
||||
|
||||
private void hideAboutHome() {
|
||||
if (!mAboutHome.getUserVisibleHint()) {
|
||||
private void animateHideHomePager() {
|
||||
hideHomePagerWithAnimation(true);
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -1381,6 +1488,30 @@ abstract public class BrowserApp extends GeckoApp
|
|||
refreshToolbarHeight();
|
||||
}
|
||||
|
||||
private void showBrowserSearch() {
|
||||
if (mBrowserSearch.getUserVisibleHint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBrowserSearchContainer.setVisibility(View.VISIBLE);
|
||||
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.search_container, mBrowserSearch, BROWSER_SEARCH_TAG).commitAllowingStateLoss();
|
||||
mBrowserSearch.setUserVisibleHint(true);
|
||||
}
|
||||
|
||||
private void hideBrowserSearch() {
|
||||
if (!mBrowserSearch.getUserVisibleHint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBrowserSearchContainer.setVisibility(View.INVISIBLE);
|
||||
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.remove(mBrowserSearch).commitAllowingStateLoss();
|
||||
mBrowserSearch.setUserVisibleHint(false);
|
||||
}
|
||||
|
||||
private class HideTabsTouchListener implements TouchEventInterceptor {
|
||||
private boolean mIsHidingTabs = false;
|
||||
|
||||
|
@ -1994,15 +2125,32 @@ abstract public class BrowserApp extends GeckoApp
|
|||
}).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
|
||||
|
|
|
@ -37,6 +37,6 @@ public class BrowserToolbarBackground extends GeckoLinearLayout {
|
|||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
setBackgroundResource(R.drawable.address_bar_bg);
|
||||
setBackgroundResource(R.drawable.url_bar_bg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -243,10 +243,10 @@ public class Favicons {
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ public class ForwardButton extends ShapedButton {
|
|||
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);
|
||||
|
@ -88,6 +88,6 @@ public class ForwardButton extends ShapedButton {
|
|||
|
||||
@Override
|
||||
public void onLightweightThemeReset() {
|
||||
setBackgroundResource(R.drawable.address_bar_nav_button);
|
||||
setBackgroundResource(R.drawable.url_bar_nav_button);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -567,8 +567,6 @@ abstract public class GeckoApp
|
|||
} 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();
|
||||
|
@ -1479,7 +1477,6 @@ abstract public class GeckoApp
|
|||
registerEventListener("Reader:Removed");
|
||||
registerEventListener("Reader:Share");
|
||||
registerEventListener("Reader:FaviconRequest");
|
||||
registerEventListener("Reader:GoToReadingList");
|
||||
registerEventListener("onCameraCapture");
|
||||
registerEventListener("Menu:Add");
|
||||
registerEventListener("Menu:Remove");
|
||||
|
@ -2028,7 +2025,6 @@ abstract public class GeckoApp
|
|||
unregisterEventListener("Reader:Removed");
|
||||
unregisterEventListener("Reader:Share");
|
||||
unregisterEventListener("Reader:FaviconRequest");
|
||||
unregisterEventListener("Reader:GoToReadingList");
|
||||
unregisterEventListener("onCameraCapture");
|
||||
unregisterEventListener("Menu:Add");
|
||||
unregisterEventListener("Menu:Remove");
|
||||
|
@ -2261,18 +2257,13 @@ abstract public class GeckoApp
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -697,8 +697,8 @@ public class GeckoAppShell
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -1069,12 +1069,12 @@ public class GeckoAppShell
|
|||
* @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);
|
||||
|
|
|
@ -61,7 +61,7 @@ final class InputMethods {
|
|||
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);
|
||||
|
|
|
@ -54,18 +54,12 @@ FENNEC_JAVA_FILES = \
|
|||
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 \
|
||||
|
@ -138,7 +132,6 @@ FENNEC_JAVA_FILES = \
|
|||
PromptInput.java \
|
||||
PromptService.java \
|
||||
Restarter.java \
|
||||
SearchEngine.java \
|
||||
sqlite/ByteBufferInputStream.java \
|
||||
sqlite/MatrixBlobCursor.java \
|
||||
sqlite/SQLiteBridge.java \
|
||||
|
@ -146,7 +139,6 @@ FENNEC_JAVA_FILES = \
|
|||
ReaderModeUtils.java \
|
||||
RemoteTabs.java \
|
||||
RobocopAPI.java \
|
||||
SearchEngineRow.java \
|
||||
ServiceNotificationClient.java \
|
||||
ScrollAnimator.java \
|
||||
SessionParser.java \
|
||||
|
@ -154,7 +146,6 @@ FENNEC_JAVA_FILES = \
|
|||
SharedPreferencesHelper.java \
|
||||
SiteIdentityPopup.java \
|
||||
SmsManager.java \
|
||||
SuggestClient.java \
|
||||
SurfaceBits.java \
|
||||
SyncPreference.java \
|
||||
Tab.java \
|
||||
|
@ -224,6 +215,36 @@ FENNEC_JAVA_FILES = \
|
|||
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 \
|
||||
|
@ -235,11 +256,7 @@ FENNEC_JAVA_FILES = \
|
|||
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 \
|
||||
|
@ -248,11 +265,6 @@ FENNEC_JAVA_FILES = \
|
|||
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 \
|
||||
|
@ -441,20 +453,13 @@ 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 \
|
||||
|
@ -462,7 +467,21 @@ RES_LAYOUT = \
|
|||
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 \
|
||||
|
@ -472,6 +491,7 @@ RES_LAYOUT = \
|
|||
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 \
|
||||
|
@ -482,6 +502,8 @@ RES_LAYOUT = \
|
|||
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 \
|
||||
|
@ -489,42 +511,41 @@ RES_LAYOUT = \
|
|||
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 \
|
||||
|
@ -560,6 +581,7 @@ RES_VALUES_LARGE_V11 = \
|
|||
$(NULL)
|
||||
|
||||
RES_VALUES_LARGE_LAND_V11 = \
|
||||
res/values-large-land-v11/dimens.xml \
|
||||
res/values-large-land-v11/styles.xml \
|
||||
$(NULL)
|
||||
|
||||
|
@ -569,10 +591,19 @@ RES_VALUES_XLARGE_V11 = \
|
|||
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 \
|
||||
|
@ -590,11 +621,8 @@ RES_XML_V11 = \
|
|||
$(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 \
|
||||
|
@ -606,17 +634,7 @@ 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 \
|
||||
|
@ -625,18 +643,10 @@ RES_DRAWABLE_MDPI = \
|
|||
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 \
|
||||
|
@ -648,7 +658,19 @@ RES_DRAWABLE_MDPI = \
|
|||
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 \
|
||||
|
@ -663,15 +685,16 @@ RES_DRAWABLE_MDPI = \
|
|||
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 \
|
||||
|
@ -692,8 +715,10 @@ RES_DRAWABLE_MDPI = \
|
|||
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 \
|
||||
|
@ -708,6 +733,7 @@ RES_DRAWABLE_MDPI = \
|
|||
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)
|
||||
|
@ -723,35 +749,17 @@ RES_DRAWABLE_HDPI = \
|
|||
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 \
|
||||
|
@ -763,7 +771,19 @@ RES_DRAWABLE_HDPI = \
|
|||
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 \
|
||||
|
@ -777,11 +797,12 @@ RES_DRAWABLE_HDPI = \
|
|||
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 \
|
||||
|
@ -802,8 +823,10 @@ RES_DRAWABLE_HDPI = \
|
|||
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 \
|
||||
|
@ -812,6 +835,7 @@ RES_DRAWABLE_HDPI = \
|
|||
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)
|
||||
|
@ -820,39 +844,22 @@ 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 \
|
||||
|
@ -864,7 +871,19 @@ RES_DRAWABLE_XHDPI = \
|
|||
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 \
|
||||
|
@ -879,8 +898,10 @@ RES_DRAWABLE_XHDPI = \
|
|||
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 \
|
||||
|
@ -909,6 +930,7 @@ RES_DRAWABLE_XHDPI = \
|
|||
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)
|
||||
|
@ -1000,6 +1022,10 @@ RES_DRAWABLE_XHDPI_V11 = \
|
|||
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 \
|
||||
|
@ -1021,42 +1047,26 @@ RES_DRAWABLE_LARGE_XHDPI_V11 = \
|
|||
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 \
|
||||
|
@ -1064,14 +1074,17 @@ RES_COLOR = \
|
|||
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 \
|
||||
|
@ -1086,25 +1099,23 @@ 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 \
|
||||
|
@ -1131,6 +1142,7 @@ RESOURCES = \
|
|||
$(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) \
|
||||
|
@ -1139,6 +1151,7 @@ RESOURCES = \
|
|||
$(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) \
|
||||
|
@ -1155,6 +1168,8 @@ RESOURCES = \
|
|||
$(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) \
|
||||
|
@ -1165,12 +1180,13 @@ RES_DIRS= \
|
|||
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 \
|
||||
|
@ -1182,9 +1198,11 @@ RES_DIRS= \
|
|||
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 \
|
||||
|
|
|
@ -146,7 +146,7 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -7,6 +7,7 @@ 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;
|
||||
|
@ -49,6 +50,7 @@ public class Tab {
|
|||
private int mHistoryIndex;
|
||||
private int mHistorySize;
|
||||
private int mParentId;
|
||||
private HomePager.Page mAboutHomePage;
|
||||
private boolean mExternal;
|
||||
private boolean mBookmark;
|
||||
private boolean mReadingListItem;
|
||||
|
@ -73,10 +75,12 @@ public class Tab {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -89,6 +93,7 @@ public class Tab {
|
|||
mUserSearch = "";
|
||||
mExternal = external;
|
||||
mParentId = parentId;
|
||||
mAboutHomePage = HomePager.Page.BOOKMARKS;
|
||||
mTitle = title == null ? "" : title;
|
||||
mFavicon = null;
|
||||
mFaviconUrl = null;
|
||||
|
@ -112,7 +117,7 @@ public class Tab {
|
|||
// 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() {
|
||||
|
@ -139,6 +144,15 @@ public class Tab {
|
|||
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;
|
||||
|
@ -607,9 +621,14 @@ public class Tab {
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -617,13 +636,6 @@ public class Tab {
|
|||
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);
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
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;
|
||||
|
@ -22,6 +24,7 @@ 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;
|
||||
|
@ -57,6 +60,7 @@ public class Tabs implements GeckoEventListener {
|
|||
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;
|
||||
|
||||
|
@ -581,6 +585,22 @@ public class Tabs implements GeckoEventListener {
|
|||
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.
|
||||
*
|
||||
|
@ -637,6 +657,7 @@ public class Tabs implements GeckoEventListener {
|
|||
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();
|
||||
|
|
|
@ -105,19 +105,11 @@ public class TabsPanel extends LinearLayout
|
|||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -315,7 +315,7 @@ public class TabsTray extends TwoWayView
|
|||
mCloseAnimationCount++;
|
||||
mPendingClosedTabs.add(view);
|
||||
|
||||
animator.setPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@Override
|
||||
public void onPropertyAnimationStart() { }
|
||||
@Override
|
||||
|
@ -353,7 +353,7 @@ public class TabsTray extends TwoWayView
|
|||
if (mOriginalSize == 0)
|
||||
mOriginalSize = (isVertical ? view.getHeight() : view.getWidth());
|
||||
|
||||
animator.setPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@Override
|
||||
public void onPropertyAnimationStart() { }
|
||||
@Override
|
||||
|
@ -377,7 +377,7 @@ public class TabsTray extends TwoWayView
|
|||
animator.attach(view, Property.TRANSLATION_Y, 0);
|
||||
|
||||
|
||||
animator.setPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@Override
|
||||
public void onPropertyAnimationStart() { }
|
||||
@Override
|
||||
|
|
|
@ -29,7 +29,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
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
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ 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;
|
||||
|
@ -49,7 +50,7 @@ public class PropertyAnimator implements Runnable {
|
|||
private long mDuration;
|
||||
private float mDurationReciprocal;
|
||||
private List<ElementHolder> mElementsList;
|
||||
private PropertyAnimationListener mListener;
|
||||
private List<PropertyAnimationListener> mListeners;
|
||||
private FramePoster mFramePoster;
|
||||
private boolean mUseHardwareLayer;
|
||||
|
||||
|
@ -64,6 +65,7 @@ public class PropertyAnimator implements Runnable {
|
|||
mElementsList = new ArrayList<ElementHolder>();
|
||||
mFramePoster = FramePoster.create(this);
|
||||
mUseHardwareLayer = true;
|
||||
mListeners = null;
|
||||
}
|
||||
|
||||
public void setUseHardwareLayer(boolean useHardwareLayer) {
|
||||
|
@ -81,8 +83,12 @@ public class PropertyAnimator implements Runnable {
|
|||
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() {
|
||||
|
@ -144,10 +150,35 @@ public class PropertyAnimator implements Runnable {
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -173,10 +204,15 @@ public class PropertyAnimator implements Runnable {
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -87,6 +87,8 @@ public class BrowserDB {
|
|||
|
||||
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);
|
||||
|
@ -237,6 +239,10 @@ public class BrowserDB {
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ 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;
|
||||
|
@ -52,7 +53,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
|
||||
// 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;
|
||||
|
@ -70,15 +70,12 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
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);
|
||||
|
@ -100,7 +97,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
@Override
|
||||
public void invalidateCachedState() {
|
||||
mDesktopBookmarksExist = null;
|
||||
mReadingListItemsExist = null;
|
||||
}
|
||||
|
||||
private Uri historyUriWithLimit(int limit) {
|
||||
|
@ -332,7 +328,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
Combined.HISTORY_ID,
|
||||
Combined.URL,
|
||||
Combined.TITLE,
|
||||
Combined.FAVICON,
|
||||
Combined.DISPLAY,
|
||||
Combined.DATE_LAST_VISITED,
|
||||
Combined.VISITS },
|
||||
|
@ -373,7 +368,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
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) {
|
||||
|
@ -382,10 +376,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
// 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) {
|
||||
|
@ -413,9 +403,9 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
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);
|
||||
|
@ -452,29 +442,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
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
|
||||
|
@ -727,6 +694,16 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
|
||||
@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;
|
||||
|
||||
|
@ -737,20 +714,19 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
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
|
||||
|
@ -777,23 +753,23 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
@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(" OR ");
|
||||
if (i > 0)
|
||||
selection.append(", ");
|
||||
|
||||
selection.append(Favicons.URL + " = ?");
|
||||
selectionArgs[i] = url;
|
||||
DatabaseUtils.appendEscapedSQLString(selection, url);
|
||||
}
|
||||
|
||||
selection.append(")");
|
||||
|
||||
return cr.query(mCombinedUriWithProfile,
|
||||
new String[] { Combined.URL, Combined.FAVICON },
|
||||
selection.toString(),
|
||||
selectionArgs,
|
||||
null);
|
||||
null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1073,12 +1049,10 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
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;
|
||||
|
@ -1087,11 +1061,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
mDesktopBookmarksIndex = mIndexOffset;
|
||||
mIndexOffset++;
|
||||
}
|
||||
|
||||
if (showReadingList) {
|
||||
mReadingListIndex = mIndexOffset;
|
||||
mIndexOffset++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1102,9 +1071,8 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
@Override
|
||||
public boolean moveToPosition(int position) {
|
||||
mAtDesktopBookmarksPosition = (mDesktopBookmarksIndex == position);
|
||||
mAtReadingListPosition = (mReadingListIndex == position);
|
||||
|
||||
if (mAtDesktopBookmarksPosition || mAtReadingListPosition)
|
||||
if (mAtDesktopBookmarksPosition)
|
||||
return true;
|
||||
|
||||
return super.moveToPosition(position - mIndexOffset);
|
||||
|
@ -1112,7 +1080,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
|
||||
@Override
|
||||
public long getLong(int columnIndex) {
|
||||
if (!mAtDesktopBookmarksPosition && !mAtReadingListPosition)
|
||||
if (!mAtDesktopBookmarksPosition)
|
||||
return super.getLong(columnIndex);
|
||||
|
||||
if (columnIndex == getColumnIndex(Bookmarks.PARENT)) {
|
||||
|
@ -1124,16 +1092,11 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
|
||||
@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;
|
||||
|
@ -1143,16 +1106,11 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
|||
|
||||
@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 "";
|
||||
}
|
||||
|
|
|
@ -265,7 +265,7 @@ public class LayerView extends FrameLayout {
|
|||
// 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)
|
||||
|
@ -273,7 +273,7 @@ public class LayerView extends FrameLayout {
|
|||
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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void load() {
|
||||
final LoaderManager manager = getLoaderManager();
|
||||
manager.initLoader(LOADER_ID_BOOKMARKS_LIST, null, mLoaderCallbacks);
|
||||
manager.initLoader(LOADER_ID_TOP_BOOKMARKS, null, mLoaderCallbacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for pinning bookmarks.
|
||||
*/
|
||||
private class PinBookmarkListener implements OnPinBookmarkListener,
|
||||
OnBookmarkSelectedListener {
|
||||
// Tag for the PinBookmarkDialog fragment.
|
||||
private static final String TAG_PIN_BOOKMARK = "pin_bookmark";
|
||||
|
||||
// Position of the pin.
|
||||
private int mPosition;
|
||||
|
||||
@Override
|
||||
public void onPinBookmark(int position) {
|
||||
mPosition = position;
|
||||
|
||||
final FragmentManager manager = getActivity().getSupportFragmentManager();
|
||||
PinBookmarkDialog dialog = (PinBookmarkDialog) manager.findFragmentByTag(TAG_PIN_BOOKMARK);
|
||||
if (dialog == null) {
|
||||
dialog = PinBookmarkDialog.newInstance();
|
||||
}
|
||||
|
||||
dialog.setOnBookmarkSelectedListener(this);
|
||||
dialog.show(manager, TAG_PIN_BOOKMARK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBookmarkSelected(final String url, final String title) {
|
||||
final int position = mPosition;
|
||||
final Context context = getActivity().getApplicationContext();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BrowserDB.pinSite(context.getContentResolver(), url, title, position);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loader for the list for bookmarks.
|
||||
*/
|
||||
private static class BookmarksLoader extends SimpleCursorLoader {
|
||||
private final int mFolderId;
|
||||
|
||||
public BookmarksLoader(Context context) {
|
||||
this(context, Bookmarks.FIXED_ROOT_ID);
|
||||
}
|
||||
|
||||
public BookmarksLoader(Context context, int folderId) {
|
||||
super(context);
|
||||
mFolderId = folderId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
return BrowserDB.getBookmarksInFolder(getContext().getContentResolver(), mFolderId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loader for the grid for top bookmarks.
|
||||
*/
|
||||
private static class TopBookmarksLoader extends SimpleCursorLoader {
|
||||
public TopBookmarksLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
final int max = getContext().getResources().getInteger(R.integer.number_of_top_sites);
|
||||
return BrowserDB.getTopSites(getContext().getContentResolver(), max);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loader callbacks for the LoaderManager of this fragment.
|
||||
*/
|
||||
private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
|
||||
public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
|
||||
super(context, loaderManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
switch(id) {
|
||||
case LOADER_ID_BOOKMARKS_LIST: {
|
||||
if (args == null) {
|
||||
return new BookmarksLoader(getActivity());
|
||||
} else {
|
||||
return new BookmarksLoader(getActivity(), args.getInt(BOOKMARKS_FOLDER_KEY));
|
||||
}
|
||||
}
|
||||
|
||||
case LOADER_ID_TOP_BOOKMARKS: {
|
||||
return new TopBookmarksLoader(getActivity());
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onCreateLoader(id, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
||||
final int loaderId = loader.getId();
|
||||
switch(loaderId) {
|
||||
case LOADER_ID_BOOKMARKS_LIST: {
|
||||
mListAdapter.swapCursor(c);
|
||||
loadFavicons(c);
|
||||
break;
|
||||
}
|
||||
|
||||
case LOADER_ID_TOP_BOOKMARKS: {
|
||||
mTopBookmarksAdapter.swapCursor(c);
|
||||
|
||||
// Load the thumbnails.
|
||||
if (c.getCount() > 0 && c.moveToFirst()) {
|
||||
final ArrayList<String> urls = new ArrayList<String>();
|
||||
do {
|
||||
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
||||
urls.add(url);
|
||||
} while (c.moveToNext());
|
||||
|
||||
if (urls.size() > 0) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls);
|
||||
getLoaderManager().restartLoader(LOADER_ID_THUMBNAILS, bundle, mThumbnailsLoaderCallbacks);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onLoadFinished(loader, c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
final int loaderId = loader.getId();
|
||||
switch(loaderId) {
|
||||
case LOADER_ID_BOOKMARKS_LIST: {
|
||||
if (mList != null) {
|
||||
mListAdapter.swapCursor(null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case LOADER_ID_TOP_BOOKMARKS: {
|
||||
if (mTopBookmarks != null) {
|
||||
mTopBookmarksAdapter.swapCursor(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onLoaderReset(loader);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFaviconsLoaded() {
|
||||
mListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An AsyncTaskLoader to load the thumbnails from a cursor.
|
||||
*/
|
||||
private static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, Thumbnail>> {
|
||||
private Map<String, Thumbnail> mThumbnails;
|
||||
private ArrayList<String> mUrls;
|
||||
|
||||
public ThumbnailsLoader(Context context, ArrayList<String> urls) {
|
||||
super(context);
|
||||
mUrls = urls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Thumbnail> loadInBackground() {
|
||||
if (mUrls == null || mUrls.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, Thumbnail> thumbnails = new HashMap<String, Thumbnail>();
|
||||
|
||||
// Query the DB for thumbnails.
|
||||
final ContentResolver cr = getContext().getContentResolver();
|
||||
final Cursor cursor = BrowserDB.getThumbnailsForUrls(cr, mUrls);
|
||||
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
do {
|
||||
// Try to get the thumbnail, if cursor is valid.
|
||||
String url = cursor.getString(cursor.getColumnIndexOrThrow(Thumbnails.URL));
|
||||
final byte[] b = cursor.getBlob(cursor.getColumnIndexOrThrow(Thumbnails.DATA));
|
||||
final Bitmap bitmap = (b == null ? null : BitmapUtils.decodeByteArray(b));
|
||||
|
||||
if (bitmap != null) {
|
||||
thumbnails.put(url, new Thumbnail(bitmap, true));
|
||||
}
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Query the DB for favicons for the urls without thumbnails.
|
||||
for (String url : mUrls) {
|
||||
if (!thumbnails.containsKey(url)) {
|
||||
final Bitmap bitmap = BrowserDB.getFaviconForUrl(cr, url);
|
||||
if (bitmap != null) {
|
||||
// Favicons.scaleImage can return several different size favicons,
|
||||
// but will at least prevent this from being too large.
|
||||
thumbnails.put(url, new Thumbnail(Favicons.getInstance().scaleImage(bitmap), false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return thumbnails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(Map<String, Thumbnail> thumbnails) {
|
||||
if (isReset()) {
|
||||
mThumbnails = null;
|
||||
return;
|
||||
}
|
||||
|
||||
mThumbnails = thumbnails;
|
||||
|
||||
if (isStarted()) {
|
||||
super.deliverResult(thumbnails);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mThumbnails != null) {
|
||||
deliverResult(mThumbnails);
|
||||
}
|
||||
|
||||
if (takeContentChanged() || mThumbnails == null) {
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
cancelLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(Map<String, Thumbnail> thumbnails) {
|
||||
mThumbnails = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
super.onReset();
|
||||
|
||||
// Ensure the loader is stopped.
|
||||
onStopLoading();
|
||||
|
||||
mThumbnails = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loader callbacks for the thumbnails on TopBookmarksView.
|
||||
*/
|
||||
private class ThumbnailsLoaderCallbacks implements LoaderCallbacks<Map<String, Thumbnail>> {
|
||||
@Override
|
||||
public Loader<Map<String, Thumbnail>> onCreateLoader(int id, Bundle args) {
|
||||
return new ThumbnailsLoader(getActivity(), args.getStringArrayList(THUMBNAILS_URLS_KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Map<String, Thumbnail>> loader, Map<String, Thumbnail> thumbnails) {
|
||||
if (mTopBookmarksAdapter != null) {
|
||||
mTopBookmarksAdapter.updateThumbnails(thumbnails);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Map<String, Thumbnail>> loader) {
|
||||
if (mTopBookmarksAdapter != null) {
|
||||
mTopBookmarksAdapter.updateThumbnails(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,878 @@
|
|||
/* -*- 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.AutocompleteHandler;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.PrefsHelper;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.home.SearchLoader.SearchCursorLoader;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
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.Bundle;
|
||||
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.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewStub;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Fragment that displays frecency search results in a ListView.
|
||||
*/
|
||||
public class BrowserSearch extends HomeFragment
|
||||
implements GeckoEventListener {
|
||||
// Logging tag name
|
||||
private static final String LOGTAG = "GeckoBrowserSearch";
|
||||
|
||||
// Cursor loader ID for search query
|
||||
private static final int LOADER_ID_SEARCH = 0;
|
||||
|
||||
// AsyncTask loader ID for suggestion query
|
||||
private static final int LOADER_ID_SUGGESTION = 1;
|
||||
|
||||
// Timeout for the suggestion client to respond
|
||||
private static final int SUGGESTION_TIMEOUT = 3000;
|
||||
|
||||
// Maximum number of results returned by the suggestion client
|
||||
private static final int SUGGESTION_MAX = 3;
|
||||
|
||||
// The maximum number of rows deep in a search we'll dig
|
||||
// for an autocomplete result
|
||||
private static final int MAX_AUTOCOMPLETE_SEARCH = 20;
|
||||
|
||||
// Duration for fade-in animation
|
||||
private static final int ANIMATION_DURATION = 250;
|
||||
|
||||
// Holds the current search term to use in the query
|
||||
private String mSearchTerm;
|
||||
|
||||
// Adapter for the list of search results
|
||||
private SearchAdapter mAdapter;
|
||||
|
||||
// The view shown by the fragment
|
||||
private LinearLayout mView;
|
||||
|
||||
// The list showing search results
|
||||
private ListView mList;
|
||||
|
||||
// Client that performs search suggestion queries
|
||||
private volatile SuggestClient mSuggestClient;
|
||||
|
||||
// List of search engines from gecko
|
||||
private ArrayList<SearchEngine> mSearchEngines;
|
||||
|
||||
// Whether search suggestions are enabled or not
|
||||
private boolean mSuggestionsEnabled;
|
||||
|
||||
// Callbacks used for the search and favicon cursor loaders
|
||||
private CursorLoaderCallbacks mCursorLoaderCallbacks;
|
||||
|
||||
// Callbacks used for the search suggestion loader
|
||||
private SuggestionLoaderCallbacks mSuggestionLoaderCallbacks;
|
||||
|
||||
// Inflater used by the adapter
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
// Autocomplete handler used when filtering results
|
||||
private AutocompleteHandler mAutocompleteHandler;
|
||||
|
||||
// On URL open listener
|
||||
private OnUrlOpenListener mUrlOpenListener;
|
||||
|
||||
// On search listener
|
||||
private OnSearchListener mSearchListener;
|
||||
|
||||
// On edit suggestion listener
|
||||
private OnEditSuggestionListener mEditSuggestionListener;
|
||||
|
||||
// Whether the suggestions will fade in when shown
|
||||
private boolean mAnimateSuggestions;
|
||||
|
||||
// Opt-in prompt view for search suggestions
|
||||
private View mSuggestionsOptInPrompt;
|
||||
|
||||
public interface OnSearchListener {
|
||||
public void onSearch(String engineId, String text);
|
||||
}
|
||||
|
||||
public interface OnEditSuggestionListener {
|
||||
public void onEditSuggestion(String suggestion);
|
||||
}
|
||||
|
||||
public static BrowserSearch newInstance() {
|
||||
return new BrowserSearch();
|
||||
}
|
||||
|
||||
public BrowserSearch() {
|
||||
mSearchTerm = "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
try {
|
||||
mUrlOpenListener = (OnUrlOpenListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement BrowserSearch.OnUrlOpenListener");
|
||||
}
|
||||
|
||||
try {
|
||||
mSearchListener = (OnSearchListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement BrowserSearch.OnSearchListener");
|
||||
}
|
||||
|
||||
try {
|
||||
mEditSuggestionListener = (OnEditSuggestionListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement BrowserSearch.OnEditSuggestionListener");
|
||||
}
|
||||
|
||||
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
|
||||
mInflater = null;
|
||||
mAutocompleteHandler = null;
|
||||
mUrlOpenListener = null;
|
||||
mSearchListener = null;
|
||||
mEditSuggestionListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mSearchEngines = new ArrayList<SearchEngine>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
mSearchEngines = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
// All list views are styled to look the same with a global activity theme.
|
||||
// If the style of the list changes, inflate it from an XML.
|
||||
mView = (LinearLayout) inflater.inflate(R.layout.browser_search, container, false);
|
||||
mList = (ListView) mView.findViewById(R.id.home_list_view);
|
||||
|
||||
return mView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
unregisterEventListener("SearchEngines:Data");
|
||||
|
||||
mView = null;
|
||||
mList = null;
|
||||
mSuggestionsOptInPrompt = null;
|
||||
mSuggestClient = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
// Account for the search engines
|
||||
position -= getSuggestEngineCount();
|
||||
final Cursor c = mAdapter.getCursor(position);
|
||||
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
||||
|
||||
// This item is a TwoLinePageRow, so we allow switch-to-tab.
|
||||
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
|
||||
}
|
||||
});
|
||||
|
||||
final ListSelectionListener listener = new ListSelectionListener();
|
||||
mList.setOnItemSelectedListener(listener);
|
||||
mList.setOnFocusChangeListener(listener);
|
||||
|
||||
mList.setOnKeyListener(new View.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, android.view.KeyEvent event) {
|
||||
final View selected = mList.getSelectedView();
|
||||
|
||||
if (selected instanceof SearchEngineRow) {
|
||||
return selected.onKeyDown(keyCode, event);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
registerForContextMenu(mList);
|
||||
registerEventListener("SearchEngines:Data");
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Get", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
final Activity activity = getActivity();
|
||||
|
||||
// Intialize the search adapter
|
||||
mAdapter = new SearchAdapter(activity);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
// Only create an instance when we need it
|
||||
mSuggestionLoaderCallbacks = null;
|
||||
|
||||
// Create callbacks before the initial loader is started
|
||||
mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
|
||||
loadIfVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String event, final JSONObject message) {
|
||||
if (event.equals("SearchEngines:Data")) {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSearchEngines(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void load() {
|
||||
getLoaderManager().initLoader(LOADER_ID_SEARCH, null, mCursorLoaderCallbacks);
|
||||
}
|
||||
|
||||
private void handleAutocomplete(String searchTerm, Cursor c) {
|
||||
if (TextUtils.isEmpty(mSearchTerm) || c == 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 boolean searchPath = (searchTerm.indexOf("/") > 0);
|
||||
final String autocompletion = findAutocompletion(searchTerm, c, searchPath);
|
||||
|
||||
if (autocompletion != null && mAutocompleteHandler != null) {
|
||||
mAutocompleteHandler.onAutocomplete(autocompletion);
|
||||
mAutocompleteHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
private String findAutocompletion(String searchTerm, Cursor c, boolean searchPath) {
|
||||
if (!c.moveToFirst()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int urlIndex = c.getColumnIndexOrThrow(URLColumns.URL);
|
||||
int searchCount = 0;
|
||||
|
||||
do {
|
||||
final Uri url = Uri.parse(c.getString(urlIndex));
|
||||
final String host = StringUtils.stripCommonSubdomains(url.getHost());
|
||||
|
||||
// Host may be null for about pages
|
||||
if (host == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final StringBuilder hostBuilder = new StringBuilder(host);
|
||||
if (hostBuilder.indexOf(searchTerm) == 0) {
|
||||
return hostBuilder.append("/").toString();
|
||||
}
|
||||
|
||||
if (searchPath) {
|
||||
final List<String> path = url.getPathSegments();
|
||||
|
||||
for (String s : path) {
|
||||
hostBuilder.append("/").append(s);
|
||||
|
||||
if (hostBuilder.indexOf(searchTerm) == 0) {
|
||||
return hostBuilder.append("/").toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
searchCount++;
|
||||
} while (searchCount < MAX_AUTOCOMPLETE_SEARCH && c.moveToNext());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void filterSuggestions() {
|
||||
if (mSuggestClient == null || !mSuggestionsEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSuggestionLoaderCallbacks == null) {
|
||||
mSuggestionLoaderCallbacks = new SuggestionLoaderCallbacks();
|
||||
}
|
||||
|
||||
getLoaderManager().restartLoader(LOADER_ID_SUGGESTION, null, mSuggestionLoaderCallbacks);
|
||||
}
|
||||
|
||||
private void setSuggestions(ArrayList<String> suggestions) {
|
||||
mSearchEngines.get(0).suggestions = suggestions;
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void setSearchEngines(JSONObject data) {
|
||||
// This method is called via a Runnable posted from the Gecko thread, so
|
||||
// it's possible the fragment and/or its view has been destroyed by the
|
||||
// time we get here. If so, just abort.
|
||||
if (mView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final JSONObject suggest = data.getJSONObject("suggest");
|
||||
final String suggestEngine = suggest.optString("engine", null);
|
||||
final String suggestTemplate = suggest.optString("template", null);
|
||||
final boolean suggestionsPrompted = suggest.getBoolean("prompted");
|
||||
final JSONArray engines = data.getJSONArray("searchEngines");
|
||||
|
||||
mSuggestionsEnabled = suggest.getBoolean("enabled");
|
||||
|
||||
ArrayList<SearchEngine> searchEngines = new ArrayList<SearchEngine>();
|
||||
for (int i = 0; i < engines.length(); i++) {
|
||||
final JSONObject engineJSON = engines.getJSONObject(i);
|
||||
final String name = engineJSON.getString("name");
|
||||
final String identifier = engineJSON.getString("identifier");
|
||||
final String iconURI = engineJSON.getString("iconURI");
|
||||
final 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();
|
||||
final boolean isPrivate = (tab != null && tab.isPrivate());
|
||||
|
||||
// Only create a new instance of SuggestClient if it hasn't been
|
||||
// set yet. e.g. Robocop tests might set it directly before search
|
||||
// engines are loaded.
|
||||
if (mSuggestClient == null && !isPrivate) {
|
||||
mSuggestClient = new SuggestClient(getActivity(), suggestTemplate,
|
||||
SUGGESTION_TIMEOUT, SUGGESTION_MAX);
|
||||
}
|
||||
} else {
|
||||
searchEngines.add(new SearchEngine(name, identifier, icon));
|
||||
}
|
||||
}
|
||||
|
||||
mSearchEngines = searchEngines;
|
||||
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// Show suggestions opt-in prompt only if user hasn't been prompted
|
||||
// and we're not on a private browsing tab.
|
||||
if (!suggestionsPrompted && mSuggestClient != null) {
|
||||
showSuggestionsOptIn();
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error getting search engine JSON", e);
|
||||
}
|
||||
|
||||
filterSuggestions();
|
||||
}
|
||||
|
||||
private void showSuggestionsOptIn() {
|
||||
mSuggestionsOptInPrompt = ((ViewStub) mView.findViewById(R.id.suggestions_opt_in_prompt)).inflate();
|
||||
|
||||
TextView promptText = (TextView) mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_title);
|
||||
promptText.setText(getResources().getString(R.string.suggestions_prompt, mSearchEngines.get(0).name));
|
||||
|
||||
final View yesButton = mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_yes);
|
||||
final View noButton = mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_no);
|
||||
|
||||
final 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 gains focus, automatically pass focus to the
|
||||
// yes button in the prompt.
|
||||
final View prompt = mSuggestionsOptInPrompt.findViewById(R.id.prompt);
|
||||
prompt.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (hasFocus) {
|
||||
yesButton.requestFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SuggestClient client = mSuggestClient;
|
||||
if (client != null) {
|
||||
client.query(mSearchTerm);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Pref observer in gecko will also set prompted = true
|
||||
PrefsHelper.setPref("browser.search.suggest.enabled", enabled);
|
||||
|
||||
TranslateAnimation slideAnimation = new TranslateAnimation(0, mSuggestionsOptInPrompt.getWidth(), 0, 0);
|
||||
slideAnimation.setDuration(ANIMATION_DURATION);
|
||||
slideAnimation.setInterpolator(new AccelerateInterpolator());
|
||||
slideAnimation.setFillAfter(true);
|
||||
final View prompt = mSuggestionsOptInPrompt.findViewById(R.id.prompt);
|
||||
|
||||
TranslateAnimation shrinkAnimation = new TranslateAnimation(0, 0, 0, -1 * mSuggestionsOptInPrompt.getHeight());
|
||||
shrinkAnimation.setDuration(ANIMATION_DURATION);
|
||||
shrinkAnimation.setFillAfter(true);
|
||||
shrinkAnimation.setStartOffset(slideAnimation.getDuration());
|
||||
shrinkAnimation.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation a) {
|
||||
// Increase the height of the view so a gap isn't shown during animation
|
||||
mView.getLayoutParams().height = mView.getHeight() +
|
||||
mSuggestionsOptInPrompt.getHeight();
|
||||
mView.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.
|
||||
mView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mView.removeView(mSuggestionsOptInPrompt);
|
||||
mList.clearAnimation();
|
||||
mSuggestionsOptInPrompt = null;
|
||||
|
||||
if (enabled) {
|
||||
// Reset the view height
|
||||
mView.getLayoutParams().height = LayoutParams.MATCH_PARENT;
|
||||
|
||||
mSuggestionsEnabled = enabled;
|
||||
mAnimateSuggestions = true;
|
||||
mAdapter.notifyDataSetChanged();
|
||||
filterSuggestions();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
prompt.startAnimation(slideAnimation);
|
||||
mSuggestionsOptInPrompt.startAnimation(shrinkAnimation);
|
||||
mList.startAnimation(shrinkAnimation);
|
||||
}
|
||||
|
||||
private int getSuggestEngineCount() {
|
||||
return (TextUtils.isEmpty(mSearchTerm) || mSuggestClient == null || !mSuggestionsEnabled) ? 0 : 1;
|
||||
}
|
||||
|
||||
private void registerEventListener(String eventName) {
|
||||
GeckoAppShell.registerEventListener(eventName, this);
|
||||
}
|
||||
|
||||
private void unregisterEventListener(String eventName) {
|
||||
GeckoAppShell.unregisterEventListener(eventName, this);
|
||||
}
|
||||
|
||||
public void filter(String searchTerm, AutocompleteHandler handler) {
|
||||
if (TextUtils.isEmpty(searchTerm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (TextUtils.equals(mSearchTerm, searchTerm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSearchTerm = searchTerm;
|
||||
mAutocompleteHandler = handler;
|
||||
|
||||
if (isVisible()) {
|
||||
// The adapter depends on the search term to determine its number
|
||||
// of items. Make it we notify the view about it.
|
||||
mAdapter.notifyDataSetChanged();
|
||||
|
||||
// Restart loaders with the new search term
|
||||
SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm, false);
|
||||
filterSuggestions();
|
||||
}
|
||||
}
|
||||
|
||||
private static class SuggestionAsyncLoader extends AsyncTaskLoader<ArrayList<String>> {
|
||||
private final SuggestClient mSuggestClient;
|
||||
private final String mSearchTerm;
|
||||
private ArrayList<String> mSuggestions;
|
||||
|
||||
public SuggestionAsyncLoader(Context context, SuggestClient suggestClient, String searchTerm) {
|
||||
super(context);
|
||||
mSuggestClient = suggestClient;
|
||||
mSearchTerm = searchTerm;
|
||||
mSuggestions = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> loadInBackground() {
|
||||
return mSuggestClient.query(mSearchTerm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(ArrayList<String> suggestions) {
|
||||
mSuggestions = suggestions;
|
||||
|
||||
if (isStarted()) {
|
||||
super.deliverResult(mSuggestions);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mSuggestions != null) {
|
||||
deliverResult(mSuggestions);
|
||||
}
|
||||
|
||||
if (takeContentChanged() || mSuggestions == null) {
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
cancelLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
super.onReset();
|
||||
|
||||
onStopLoading();
|
||||
mSuggestions = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class SearchAdapter extends MultiTypeCursorAdapter {
|
||||
private static final int ROW_SEARCH = 0;
|
||||
private static final int ROW_STANDARD = 1;
|
||||
private static final int ROW_SUGGEST = 2;
|
||||
|
||||
public SearchAdapter(Context context) {
|
||||
super(context, null, new int[] { ROW_STANDARD,
|
||||
ROW_SEARCH,
|
||||
ROW_SUGGEST },
|
||||
new int[] { R.layout.home_item_row,
|
||||
R.layout.home_search_item_row,
|
||||
R.layout.home_search_item_row });
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
final 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 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 (!mList.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.
|
||||
final int index = getEngineIndex(position);
|
||||
if (index != -1) {
|
||||
return mSearchEngines.get(index).suggestions.isEmpty();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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 (TextUtils.isEmpty(mSearchTerm)) {
|
||||
return resultCount;
|
||||
}
|
||||
|
||||
return resultCount + mSearchEngines.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, int position) {
|
||||
final int type = getItemViewType(position);
|
||||
|
||||
if (type == ROW_SEARCH || type == ROW_SUGGEST) {
|
||||
final SearchEngineRow row = (SearchEngineRow) view;
|
||||
row.setOnUrlOpenListener(mUrlOpenListener);
|
||||
row.setOnSearchListener(mSearchListener);
|
||||
row.setOnEditSuggestionListener(mEditSuggestionListener);
|
||||
row.setSearchTerm(mSearchTerm);
|
||||
|
||||
final SearchEngine engine = mSearchEngines.get(getEngineIndex(position));
|
||||
final boolean animate = (mAnimateSuggestions && engine.suggestions.size() > 0);
|
||||
row.updateFromSearchEngine(engine, animate);
|
||||
if (animate) {
|
||||
// Only animate suggestions the first time they are shown
|
||||
mAnimateSuggestions = false;
|
||||
}
|
||||
} else {
|
||||
// Account for the search engines
|
||||
position -= getSuggestEngineCount();
|
||||
|
||||
final Cursor c = getCursor(position);
|
||||
final TwoLinePageRow row = (TwoLinePageRow) view;
|
||||
row.updateFromCursor(c);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
|
||||
public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
|
||||
super(context, loaderManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
if (id == LOADER_ID_SEARCH) {
|
||||
return SearchLoader.createInstance(getActivity(), args);
|
||||
} else {
|
||||
return super.onCreateLoader(id, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
||||
if (loader.getId() == LOADER_ID_SEARCH) {
|
||||
mAdapter.swapCursor(c);
|
||||
|
||||
// We should handle autocompletion based on the search term
|
||||
// associated with the currently loader that has just provided
|
||||
// the results.
|
||||
SearchCursorLoader searchLoader = (SearchCursorLoader) loader;
|
||||
handleAutocomplete(searchLoader.getSearchTerm(), c);
|
||||
|
||||
loadFavicons(c);
|
||||
} else {
|
||||
super.onLoadFinished(loader, c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
if (loader.getId() == LOADER_ID_SEARCH) {
|
||||
mAdapter.swapCursor(null);
|
||||
} else {
|
||||
super.onLoaderReset(loader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFaviconsLoaded() {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private class SuggestionLoaderCallbacks implements LoaderCallbacks<ArrayList<String>> {
|
||||
@Override
|
||||
public Loader<ArrayList<String>> onCreateLoader(int id, Bundle args) {
|
||||
// mSuggestClient is set to null in onDestroyView(), so using it
|
||||
// safely here relies on the fact that onCreateLoader() is called
|
||||
// synchronously in restartLoader().
|
||||
return new SuggestionAsyncLoader(getActivity(), mSuggestClient, mSearchTerm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<ArrayList<String>> loader, ArrayList<String> suggestions) {
|
||||
setSuggestions(suggestions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<ArrayList<String>> loader) {
|
||||
setSuggestions(new ArrayList<String>());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HomeSearchListView is a list view for displaying search engine results on the awesome screen.
|
||||
*/
|
||||
public static class HomeSearchListView extends HomeListView {
|
||||
public HomeSearchListView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.homeListViewStyle);
|
||||
}
|
||||
|
||||
public HomeSearchListView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
// Dismiss the soft keyboard.
|
||||
requestFocus();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/* -*- 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 android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Shader;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
/**
|
||||
* FadedTextView fades the ends of the text by fadeWidth amount,
|
||||
* if the text is too long and requires an ellipsis.
|
||||
*/
|
||||
public class FadedTextView extends TextView {
|
||||
|
||||
// Width of the fade effect from end of the view.
|
||||
private int mFadeWidth;
|
||||
|
||||
public FadedTextView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public FadedTextView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, android.R.attr.textViewStyle);
|
||||
}
|
||||
|
||||
public FadedTextView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FadedTextView);
|
||||
mFadeWidth = a.getDimensionPixelSize(R.styleable.FadedTextView_fadeWidth, 0);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
int width = getMeasuredWidth();
|
||||
|
||||
// Layout doesn't return a proper width for getWidth().
|
||||
// Instead check the width of the first line, as we've restricted to just one line.
|
||||
if (getLayout().getLineWidth(0) > width) {
|
||||
int color = getCurrentTextColor();
|
||||
float stop = ((float) (width - mFadeWidth) / (float) width);
|
||||
LinearGradient gradient = new LinearGradient(0, 0, width, 0,
|
||||
new int[] { color, color, 0x0 },
|
||||
new float[] { 0, stop, 1.0f },
|
||||
Shader.TileMode.CLAMP);
|
||||
getPaint().setShader(gradient);
|
||||
} else {
|
||||
getPaint().setShader(null);
|
||||
}
|
||||
|
||||
// Do a default draw.
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/* -*- 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.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Encapsulates the implementation of the favicons cursorloader.
|
||||
*/
|
||||
class FaviconsLoader {
|
||||
// Argument containing list of urls for the favicons loader
|
||||
private static final String FAVICONS_LOADER_URLS_ARG = "urls";
|
||||
|
||||
private FaviconsLoader() {
|
||||
}
|
||||
|
||||
private static ArrayList<String> getUrlsWithoutFavicon(Cursor c) {
|
||||
ArrayList<String> urls = new ArrayList<String>();
|
||||
|
||||
if (c == null || !c.moveToFirst()) {
|
||||
return urls;
|
||||
}
|
||||
|
||||
final Favicons favicons = Favicons.getInstance();
|
||||
|
||||
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. The url is null for bookmark folders.
|
||||
if (url == null || favicons.getFaviconFromMemCache(url) != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
urls.add(url);
|
||||
} while (c.moveToNext());
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
private static void storeFaviconsInMemCache(Cursor c) {
|
||||
if (c == null || !c.moveToFirst()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Favicons favicons = Favicons.getInstance();
|
||||
|
||||
do {
|
||||
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
||||
final byte[] b = c.getBlob(c.getColumnIndexOrThrow(URLColumns.FAVICON));
|
||||
|
||||
if (b == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Bitmap favicon = BitmapUtils.decodeByteArray(b);
|
||||
if (favicon == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
favicon = favicons.scaleImage(favicon);
|
||||
favicons.putFaviconInMemCache(url, favicon);
|
||||
} while (c.moveToNext());
|
||||
}
|
||||
|
||||
public static void restartFromCursor(LoaderManager manager, int loaderId,
|
||||
LoaderCallbacks<Cursor> callbacks, Cursor c) {
|
||||
// If there urls without in-memory favicons, trigger a new loader
|
||||
// to load the images from disk to memory.
|
||||
ArrayList<String> urls = getUrlsWithoutFavicon(c);
|
||||
if (urls.size() > 0) {
|
||||
Bundle args = new Bundle();
|
||||
args.putStringArrayList(FAVICONS_LOADER_URLS_ARG, urls);
|
||||
|
||||
manager.restartLoader(loaderId, args, callbacks);
|
||||
}
|
||||
}
|
||||
|
||||
public static Loader<Cursor> createInstance(Context context, Bundle args) {
|
||||
final ArrayList<String> urls = args.getStringArrayList(FAVICONS_LOADER_URLS_ARG);
|
||||
return new FaviconsCursorLoader(context, urls);
|
||||
}
|
||||
|
||||
private static class FaviconsCursorLoader extends SimpleCursorLoader {
|
||||
private final ArrayList<String> mUrls;
|
||||
|
||||
public FaviconsCursorLoader(Context context, ArrayList<String> urls) {
|
||||
super(context);
|
||||
mUrls = urls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
final ContentResolver cr = getContext().getContentResolver();
|
||||
|
||||
Cursor c = BrowserDB.getFaviconsForUrls(cr, mUrls);
|
||||
storeFaviconsInMemCache(c);
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/* -*- 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.widget.IconTabWidget;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import android.widget.ImageButton;
|
||||
|
||||
public class HistoryPage extends HomeFragment
|
||||
implements IconTabWidget.OnTabChangedListener {
|
||||
// Logging tag name
|
||||
private static final String LOGTAG = "GeckoHistoryPage";
|
||||
private IconTabWidget mTabWidget;
|
||||
private int mSelectedTab;
|
||||
private boolean initializeVisitedPage;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.home_history_page, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
mTabWidget = (IconTabWidget) view.findViewById(R.id.tab_icon_widget);
|
||||
|
||||
mTabWidget.addTab(R.drawable.icon_most_visited, R.string.home_most_visited_title);
|
||||
mTabWidget.addTab(R.drawable.icon_most_recent, R.string.home_most_recent_title);
|
||||
mTabWidget.addTab(R.drawable.icon_last_tabs, R.string.home_last_tabs_title);
|
||||
|
||||
mTabWidget.setTabSelectionListener(this);
|
||||
mTabWidget.setCurrentTab(mSelectedTab);
|
||||
|
||||
loadIfVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
// Show most visited page as the initial page.
|
||||
// Since we detach/attach on config change, this prevents from replacing current fragment.
|
||||
if (!initializeVisitedPage) {
|
||||
showMostVisitedPage();
|
||||
initializeVisitedPage = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabChanged(int index) {
|
||||
if (index == mSelectedTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
showMostVisitedPage();
|
||||
} else if (index == 1) {
|
||||
showMostRecentPage();
|
||||
} else if (index == 2) {
|
||||
showLastTabsPage();
|
||||
}
|
||||
|
||||
mTabWidget.setCurrentTab(index);
|
||||
mSelectedTab = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
// Rotation should detach and re-attach to use a different layout.
|
||||
if (isVisible()) {
|
||||
getFragmentManager().beginTransaction()
|
||||
.detach(this)
|
||||
.attach(this)
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
|
||||
private void showSubPage(Fragment subPage) {
|
||||
final Bundle args = new Bundle();
|
||||
args.putBoolean(HomePager.CAN_LOAD_ARG, getCanLoadHint());
|
||||
subPage.setArguments(args);
|
||||
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.addToBackStack(null).replace(R.id.visited_page_container, subPage)
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
|
||||
private void showMostVisitedPage() {
|
||||
final MostVisitedPage mostVisitedPage = MostVisitedPage.newInstance();
|
||||
showSubPage(mostVisitedPage);
|
||||
}
|
||||
|
||||
private void showMostRecentPage() {
|
||||
final MostRecentPage mostRecentPage = MostRecentPage.newInstance();
|
||||
showSubPage(mostRecentPage);
|
||||
}
|
||||
|
||||
private void showLastTabsPage() {
|
||||
final LastTabsPage lastTabsPage = LastTabsPage.newInstance();
|
||||
showSubPage(lastTabsPage);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/* -*- 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 android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
|
||||
/**
|
||||
* Cursor loader callbacks that takes care loading favicons into memory.
|
||||
*/
|
||||
abstract class HomeCursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
|
||||
|
||||
// Cursor loader ID for favicons query
|
||||
private static final int LOADER_ID_FAVICONS = 100;
|
||||
|
||||
private final Context mContext;
|
||||
private final LoaderManager mLoaderManager;
|
||||
|
||||
public HomeCursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
|
||||
mContext = context;
|
||||
mLoaderManager = loaderManager;
|
||||
}
|
||||
|
||||
public void loadFavicons(Cursor cursor) {
|
||||
FaviconsLoader.restartFromCursor(mLoaderManager, LOADER_ID_FAVICONS, this, cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
if (id == LOADER_ID_FAVICONS) {
|
||||
return FaviconsLoader.createInstance(mContext, args);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
||||
if (loader.getId() == LOADER_ID_FAVICONS) {
|
||||
onFaviconsLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// Do nothing by default.
|
||||
}
|
||||
|
||||
// Callback for favicons loaded in memory.
|
||||
public abstract void onFaviconsLoaded();
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
/* -*- 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.EditBookmarkDialog;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.ReaderModeUtils;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UiAsyncTask;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* HomeFragment is an empty fragment that can be added to the HomePager.
|
||||
* Subclasses can add their own views.
|
||||
*/
|
||||
abstract class HomeFragment extends Fragment {
|
||||
// Log Tag.
|
||||
private static final String LOGTAG="GeckoHomeFragment";
|
||||
|
||||
// Share MIME type.
|
||||
private static final String SHARE_MIME_TYPE = "text/plain";
|
||||
|
||||
// URL to Title replacement regex.
|
||||
private static final String REGEX_URL_TO_TITLE = "^([a-z]+://)?(www\\.)?";
|
||||
|
||||
// Whether the fragment can load its content or not
|
||||
// This is used to defer data loading until the editing
|
||||
// mode animation ends.
|
||||
private boolean mCanLoadHint;
|
||||
|
||||
// Whether the fragment has loaded its content
|
||||
private boolean mIsLoaded;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final Bundle args = getArguments();
|
||||
if (args != null) {
|
||||
mCanLoadHint = args.getBoolean(HomePager.CAN_LOAD_ARG, false);
|
||||
} else {
|
||||
mCanLoadHint = false;
|
||||
}
|
||||
|
||||
mIsLoaded = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
|
||||
if (menuInfo == null || !(menuInfo instanceof HomeContextMenuInfo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
|
||||
|
||||
// Don't show the context menu for folders.
|
||||
if (info.isFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
MenuInflater inflater = new MenuInflater(view.getContext());
|
||||
inflater.inflate(R.menu.home_contextmenu, menu);
|
||||
|
||||
menu.setHeaderTitle(info.title);
|
||||
|
||||
// Hide the "Edit" menuitem if this item isn't a bookmark.
|
||||
if (info.bookmarkId < 0) {
|
||||
menu.findItem(R.id.home_edit_bookmark).setVisible(false);
|
||||
}
|
||||
|
||||
// Hide the "Remove" menuitem if this item doesn't have a bookmark or history ID.
|
||||
if (info.bookmarkId < 0 && info.historyId < 0) {
|
||||
menu.findItem(R.id.home_remove).setVisible(false);
|
||||
}
|
||||
|
||||
final boolean canOpenInReader = (info.display == Combined.DISPLAY_READER);
|
||||
menu.findItem(R.id.home_open_in_reader).setVisible(canOpenInReader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
// onContextItemSelected() is first dispatched to the activity and
|
||||
// then dispatched to its fragments. Since fragments cannot "override"
|
||||
// menu item selection handling, it's better to avoid menu id collisions
|
||||
// between the activity and its fragments.
|
||||
|
||||
ContextMenuInfo menuInfo = item.getMenuInfo();
|
||||
if (menuInfo == null || !(menuInfo instanceof HomeContextMenuInfo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
|
||||
final Context context = getActivity().getApplicationContext();
|
||||
|
||||
final int itemId = item.getItemId();
|
||||
if (itemId == R.id.home_share) {
|
||||
if (info.url == null) {
|
||||
Log.e(LOGTAG, "Can't share because URL is null");
|
||||
} else {
|
||||
GeckoAppShell.openUriExternal(info.url, SHARE_MIME_TYPE, "", "",
|
||||
Intent.ACTION_SEND, info.title);
|
||||
}
|
||||
}
|
||||
|
||||
if (itemId == R.id.home_add_to_launcher) {
|
||||
if (info.url == null) {
|
||||
Log.e(LOGTAG, "Can't add to home screen because URL is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME: bug 897772
|
||||
Bitmap bitmap = null;
|
||||
if (info.favicon != null) {
|
||||
bitmap = BitmapUtils.decodeByteArray(info.favicon);
|
||||
}
|
||||
|
||||
String shortcutTitle = TextUtils.isEmpty(info.title) ? info.url.replaceAll(REGEX_URL_TO_TITLE, "") : info.title;
|
||||
GeckoAppShell.createShortcut(shortcutTitle, info.url, bitmap, "");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.home_open_private_tab || itemId == R.id.home_open_new_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.home_open_private_tab)
|
||||
flags |= Tabs.LOADURL_PRIVATE;
|
||||
|
||||
final String url = (info.inReadingList ? ReaderModeUtils.getAboutReaderForUrl(info.url, true) : info.url);
|
||||
Tabs.getInstance().loadUrl(url, flags);
|
||||
Toast.makeText(context, R.string.new_tab_opened, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.home_edit_bookmark) {
|
||||
// UI Dialog associates to the activity context, not the applications'.
|
||||
new EditBookmarkDialog(getActivity()).show(info.url);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.home_open_in_reader) {
|
||||
final String url = ReaderModeUtils.getAboutReaderForUrl(info.url, true);
|
||||
Tabs.getInstance().loadUrl(url, Tabs.LOADURL_NONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.home_remove) {
|
||||
// Prioritize removing a history entry over a bookmark in the case of a combined item.
|
||||
final int historyId = info.historyId;
|
||||
if (historyId > -1) {
|
||||
new RemoveHistoryTask(context, historyId).execute();
|
||||
return true;
|
||||
}
|
||||
|
||||
final int bookmarkId = info.bookmarkId;
|
||||
if (bookmarkId > -1) {
|
||||
new RemoveBookmarkTask(context, bookmarkId, info.url, info.inReadingList).execute();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint (boolean isVisibleToUser) {
|
||||
if (isVisibleToUser == getUserVisibleHint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
loadIfVisible();
|
||||
}
|
||||
|
||||
void setCanLoadHint(boolean canLoadHint) {
|
||||
if (mCanLoadHint == canLoadHint) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCanLoadHint = canLoadHint;
|
||||
loadIfVisible();
|
||||
}
|
||||
|
||||
boolean getCanLoadHint() {
|
||||
return mCanLoadHint;
|
||||
}
|
||||
|
||||
protected abstract void load();
|
||||
|
||||
protected void loadIfVisible() {
|
||||
if (!mCanLoadHint || !isVisible() || !getUserVisibleHint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mIsLoaded) {
|
||||
load();
|
||||
mIsLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoveBookmarkTask extends UiAsyncTask<Void, Void, Void> {
|
||||
private final Context mContext;
|
||||
private final int mId;
|
||||
private final String mUrl;
|
||||
private final boolean mInReadingList;
|
||||
|
||||
public RemoveBookmarkTask(Context context, int id, String url, boolean inReadingList) {
|
||||
super(ThreadUtils.getBackgroundHandler());
|
||||
|
||||
mContext = context;
|
||||
mId = id;
|
||||
mUrl = url;
|
||||
mInReadingList = inReadingList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void doInBackground(Void... params) {
|
||||
ContentResolver cr = mContext.getContentResolver();
|
||||
BrowserDB.removeBookmark(cr, mId);
|
||||
if (mInReadingList) {
|
||||
GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Remove", mUrl);
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
|
||||
int count = BrowserDB.getReadingListCount(cr);
|
||||
e = GeckoEvent.createBroadcastEvent("Reader:ListCountUpdated", Integer.toString(count));
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(Void result) {
|
||||
int messageId = mInReadingList ? R.string.reading_list_removed : R.string.bookmark_removed;
|
||||
Toast.makeText(mContext, messageId, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoveHistoryTask extends UiAsyncTask<Void, Void, Void> {
|
||||
private final Context mContext;
|
||||
private final int mId;
|
||||
|
||||
public RemoveHistoryTask(Context context, int id) {
|
||||
super(ThreadUtils.getBackgroundHandler());
|
||||
|
||||
mContext = context;
|
||||
mId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void doInBackground(Void... params) {
|
||||
BrowserDB.removeHistoryEntry(mContext.getContentResolver(), mId);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(Void result) {
|
||||
Toast.makeText(mContext, R.string.history_removed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
/* -*- 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.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.BrowserContract.URLColumns;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.AbsListView.LayoutParams;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemLongClickListener;
|
||||
import android.widget.ListView;
|
||||
|
||||
/**
|
||||
* HomeListView is a custom extension of ListView, that packs a HomeContextMenuInfo
|
||||
* when any of its rows is long pressed.
|
||||
*/
|
||||
public class HomeListView extends ListView
|
||||
implements OnItemLongClickListener {
|
||||
|
||||
// ContextMenuInfo associated with the currently long pressed list item.
|
||||
private HomeContextMenuInfo mContextMenuInfo;
|
||||
|
||||
// On URL open listener
|
||||
private OnUrlOpenListener mUrlOpenListener;
|
||||
|
||||
// Top divider
|
||||
private boolean mShowTopDivider;
|
||||
|
||||
public HomeListView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public HomeListView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.homeListViewStyle);
|
||||
}
|
||||
|
||||
public HomeListView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HomeListView, defStyle, 0);
|
||||
mShowTopDivider = a.getBoolean(R.styleable.HomeListView_topDivider, false);
|
||||
a.recycle();
|
||||
|
||||
setOnItemLongClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
final Drawable divider = getDivider();
|
||||
if (mShowTopDivider && divider != null) {
|
||||
final int dividerHeight = getDividerHeight();
|
||||
final View view = new View(getContext());
|
||||
view.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, dividerHeight));
|
||||
addHeaderView(view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
mUrlOpenListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Object item = parent.getItemAtPosition(position);
|
||||
|
||||
// HomeListView could hold headers too. Add a context menu info only for its children.
|
||||
if (item instanceof Cursor) {
|
||||
Cursor cursor = (Cursor) item;
|
||||
mContextMenuInfo = new HomeContextMenuInfo(view, position, id, cursor);
|
||||
return showContextMenuForChild(HomeListView.this);
|
||||
} else {
|
||||
mContextMenuInfo = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContextMenuInfo getContextMenuInfo() {
|
||||
return mContextMenuInfo;
|
||||
}
|
||||
|
||||
public OnUrlOpenListener getOnUrlOpenListener() {
|
||||
return mUrlOpenListener;
|
||||
}
|
||||
|
||||
public void setOnUrlOpenListener(OnUrlOpenListener listener) {
|
||||
mUrlOpenListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* A ContextMenuInfo for HomeListView that adds details from the cursor.
|
||||
*/
|
||||
public static class HomeContextMenuInfo extends AdapterContextMenuInfo {
|
||||
public int bookmarkId;
|
||||
public int historyId;
|
||||
public String url;
|
||||
public byte[] favicon;
|
||||
public String title;
|
||||
public int display;
|
||||
public boolean isFolder;
|
||||
public boolean inReadingList;
|
||||
|
||||
/**
|
||||
* This constructor assumes that the cursor was generated from a query
|
||||
* to either the combined view or the bookmarks table.
|
||||
*/
|
||||
public HomeContextMenuInfo(View targetView, int position, long id, Cursor cursor) {
|
||||
super(targetView, position, id);
|
||||
|
||||
if (cursor == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int typeCol = cursor.getColumnIndex(Bookmarks.TYPE);
|
||||
if (typeCol != -1) {
|
||||
isFolder = (cursor.getInt(typeCol) == Bookmarks.TYPE_FOLDER);
|
||||
} else {
|
||||
isFolder = false;
|
||||
}
|
||||
|
||||
// We don't show a context menu for folders, so return early.
|
||||
if (isFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
|
||||
title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
|
||||
|
||||
final int bookmarkIdCol = cursor.getColumnIndex(Combined.BOOKMARK_ID);
|
||||
if (bookmarkIdCol == -1) {
|
||||
// If there isn't a bookmark ID column, this must be a bookmarks cursor,
|
||||
// so the regular ID column will correspond to a bookmark ID.
|
||||
bookmarkId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
|
||||
} else if (cursor.isNull(bookmarkIdCol)) {
|
||||
// If this is a combined cursor, we may get a history item without a
|
||||
// bookmark, in which case the bookmarks ID column value will be null.
|
||||
bookmarkId = -1;
|
||||
} else {
|
||||
bookmarkId = cursor.getInt(bookmarkIdCol);
|
||||
}
|
||||
|
||||
final int historyIdCol = cursor.getColumnIndex(Combined.HISTORY_ID);
|
||||
if (historyIdCol != -1) {
|
||||
historyId = cursor.getInt(historyIdCol);
|
||||
} else {
|
||||
historyId = -1;
|
||||
}
|
||||
|
||||
final int faviconCol = cursor.getColumnIndex(Combined.FAVICON);
|
||||
if (faviconCol != -1) {
|
||||
favicon = cursor.getBlob(faviconCol);
|
||||
} else {
|
||||
favicon = null;
|
||||
}
|
||||
|
||||
// We only have the parent column in cursors from getBookmarksInFolder.
|
||||
final int parentCol = cursor.getColumnIndex(Bookmarks.PARENT);
|
||||
if (parentCol != -1) {
|
||||
inReadingList = (cursor.getInt(parentCol) == Bookmarks.FIXED_READING_LIST_ID);
|
||||
} else {
|
||||
inReadingList = false;
|
||||
}
|
||||
|
||||
final int displayCol = cursor.getColumnIndex(Combined.DISPLAY);
|
||||
if (displayCol != -1) {
|
||||
display = cursor.getInt(displayCol);
|
||||
} else {
|
||||
display = Combined.DISPLAY_NORMAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
/* -*- 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.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||
import android.support.v4.view.PagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class HomePager extends ViewPager {
|
||||
// Subpage fragment tag
|
||||
public static final String SUBPAGE_TAG = "home_pager_subpage";
|
||||
|
||||
private final Context mContext;
|
||||
private volatile boolean mLoaded;
|
||||
private Decor mDecor;
|
||||
|
||||
// List of pages in order.
|
||||
public enum Page {
|
||||
HISTORY,
|
||||
BOOKMARKS,
|
||||
READING_LIST
|
||||
}
|
||||
|
||||
// This is mostly used by UI tests to easily fetch
|
||||
// specific list views at runtime.
|
||||
static final String LIST_TAG_HISTORY = "history";
|
||||
static final String LIST_TAG_BOOKMARKS = "bookmarks";
|
||||
static final String LIST_TAG_READING_LIST = "reading_list";
|
||||
static final String LIST_TAG_MOST_VISITED = "most_visited";
|
||||
static final String LIST_TAG_MOST_RECENT = "most_recent";
|
||||
static final String LIST_TAG_LAST_TABS = "last_tabs";
|
||||
|
||||
private EnumMap<Page, Fragment> mPages = new EnumMap<Page, Fragment>(Page.class);
|
||||
|
||||
public interface OnUrlOpenListener {
|
||||
public enum Flags {
|
||||
ALLOW_SWITCH_TO_TAB
|
||||
}
|
||||
|
||||
public void onUrlOpen(String url, EnumSet<Flags> flags);
|
||||
}
|
||||
|
||||
public interface OnNewTabsListener {
|
||||
public void onNewTabs(String[] urls);
|
||||
}
|
||||
|
||||
interface OnTitleClickListener {
|
||||
public void onTitleClicked(int index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Special type of child views that could be added as pager decorations by default.
|
||||
*/
|
||||
interface Decor {
|
||||
public void onAddPagerView(String title);
|
||||
public void removeAllPagerViews();
|
||||
public void onPageSelected(int position);
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
|
||||
public void setOnTitleClickListener(OnTitleClickListener onTitleClickListener);
|
||||
}
|
||||
|
||||
static final String CAN_LOAD_ARG = "canLoad";
|
||||
|
||||
public HomePager(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public HomePager(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
|
||||
// This is to keep all 3 pages in memory after they are
|
||||
// selected in the pager.
|
||||
setOffscreenPageLimit(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
||||
if (child instanceof Decor) {
|
||||
((ViewPager.LayoutParams) params).isDecor = true;
|
||||
mDecor = (Decor) child;
|
||||
setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
mDecor.onPageSelected(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
mDecor.onPageScrolled(position, positionOffset, positionOffsetPixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) { }
|
||||
});
|
||||
}
|
||||
|
||||
super.addView(child, index, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and initializes the pager.
|
||||
*
|
||||
* @param fm FragmentManager for the adapter
|
||||
*/
|
||||
public void show(FragmentManager fm, Page page, PropertyAnimator animator) {
|
||||
mLoaded = true;
|
||||
final TabsAdapter adapter = new TabsAdapter(fm);
|
||||
|
||||
// Only animate on post-HC devices, when a non-null animator is given
|
||||
final boolean shouldAnimate = (animator != null && Build.VERSION.SDK_INT >= 11);
|
||||
|
||||
// Add the pages to the adapter in order.
|
||||
adapter.addTab(Page.HISTORY, HistoryPage.class, new Bundle(),
|
||||
getContext().getString(R.string.home_history_title));
|
||||
adapter.addTab(Page.BOOKMARKS, BookmarksPage.class, new Bundle(),
|
||||
getContext().getString(R.string.bookmarks_title));
|
||||
adapter.addTab(Page.READING_LIST, ReadingListPage.class, new Bundle(),
|
||||
getContext().getString(R.string.reading_list_title));
|
||||
|
||||
adapter.setCanLoadHint(!shouldAnimate);
|
||||
|
||||
setAdapter(adapter);
|
||||
|
||||
setCurrentItem(adapter.getItemPosition(page), false);
|
||||
setVisibility(VISIBLE);
|
||||
|
||||
if (shouldAnimate) {
|
||||
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@Override
|
||||
public void onPropertyAnimationStart() {
|
||||
setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertyAnimationEnd() {
|
||||
setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
adapter.setCanLoadHint(true);
|
||||
}
|
||||
});
|
||||
|
||||
ViewHelper.setAlpha(this, 0.0f);
|
||||
|
||||
animator.attach(this,
|
||||
PropertyAnimator.Property.ALPHA,
|
||||
1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the pager and removes all child fragments.
|
||||
*/
|
||||
public void hide() {
|
||||
mLoaded = false;
|
||||
setVisibility(GONE);
|
||||
setAdapter(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the pager is visible.
|
||||
*
|
||||
* Unlike getVisibility(), this method does not need to be called on the UI
|
||||
* thread.
|
||||
*
|
||||
* @return Whether the pager and its fragments are being displayed
|
||||
*/
|
||||
public boolean isVisible() {
|
||||
return mLoaded;
|
||||
}
|
||||
|
||||
class TabsAdapter extends FragmentStatePagerAdapter
|
||||
implements OnTitleClickListener {
|
||||
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
|
||||
|
||||
final class TabInfo {
|
||||
private final Page page;
|
||||
private final Class<?> clss;
|
||||
private final Bundle args;
|
||||
private final String title;
|
||||
|
||||
TabInfo(Page page, Class<?> clss, Bundle args, String title) {
|
||||
this.page = page;
|
||||
this.clss = clss;
|
||||
this.args = args;
|
||||
this.title = title;
|
||||
}
|
||||
}
|
||||
|
||||
public TabsAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
|
||||
if (mDecor != null) {
|
||||
mDecor.removeAllPagerViews();
|
||||
mDecor.setOnTitleClickListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void addTab(Page page, Class<?> clss, Bundle args, String title) {
|
||||
TabInfo info = new TabInfo(page, clss, args, title);
|
||||
mTabs.add(info);
|
||||
notifyDataSetChanged();
|
||||
|
||||
if (mDecor != null) {
|
||||
mDecor.onAddPagerView(title);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTitleClicked(int index) {
|
||||
setCurrentItem(index, true);
|
||||
}
|
||||
|
||||
public int getItemPosition(Page page) {
|
||||
for (int i = 0; i < mTabs.size(); i++) {
|
||||
TabInfo info = mTabs.get(i);
|
||||
if (info.page == page) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mTabs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
TabInfo info = mTabs.get(position);
|
||||
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
TabInfo info = mTabs.get(position);
|
||||
return info.title.toUpperCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object instantiateItem(ViewGroup container, int position) {
|
||||
Fragment fragment = (Fragment) super.instantiateItem(container, position);
|
||||
|
||||
mPages.put(mTabs.get(position).page, fragment);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
super.destroyItem(container, position, object);
|
||||
|
||||
mPages.remove(mTabs.get(position).page);
|
||||
}
|
||||
|
||||
public void setCanLoadHint(boolean canLoadHint) {
|
||||
// Update fragment arguments for future instances
|
||||
for (TabInfo info : mTabs) {
|
||||
info.args.putBoolean(CAN_LOAD_ARG, canLoadHint);
|
||||
}
|
||||
|
||||
// Enable/disable loading on all existing pages
|
||||
for (Fragment page : mPages.values()) {
|
||||
final HomeFragment homePage = (HomeFragment) page;
|
||||
homePage.setCanLoadHint(canLoadHint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
// XXX: Drop the soft keyboard by stealing focus. Note that the HomePager (via XML
|
||||
// attr) is focusable after its descendants allowing requestFocus to succeed and drop
|
||||
// the soft keyboard even if there are no other focusable views on the screen (e.g.
|
||||
// the Reading List is empty).
|
||||
requestFocus();
|
||||
}
|
||||
|
||||
return super.onInterceptTouchEvent(event);
|
||||
}
|
||||
}
|
|
@ -0,0 +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.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.v4.view.PagerTabStrip;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
/**
|
||||
* HomePagerTabStrip is a custom implementation of PagerTabStrip
|
||||
* that exposes XML attributes for the public methods.
|
||||
*/
|
||||
|
||||
class HomePagerTabStrip extends PagerTabStrip {
|
||||
|
||||
public HomePagerTabStrip(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public HomePagerTabStrip(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HomePagerTabStrip);
|
||||
int color = a.getColor(R.styleable.HomePagerTabStrip_tabIndicatorColor, 0x00);
|
||||
a.recycle();
|
||||
|
||||
setTabIndicatorColor(color);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
/* -*- 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.GeckoProfile;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.SessionParser;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.home.HomePager.OnNewTabsListener;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MatrixCursor.RowBuilder;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewStub;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Fragment that displays tabs from last session in a ListView.
|
||||
*/
|
||||
public class LastTabsPage extends HomeFragment {
|
||||
// Logging tag name
|
||||
private static final String LOGTAG = "GeckoLastTabsPage";
|
||||
|
||||
// Cursor loader ID for the session parser
|
||||
private static final int LOADER_ID_LAST_TABS = 0;
|
||||
|
||||
// Adapter for the list of search results
|
||||
private LastTabsAdapter mAdapter;
|
||||
|
||||
// The view shown by the fragment.
|
||||
private ListView mList;
|
||||
|
||||
// The title for this HomeFragment page.
|
||||
private TextView mTitle;
|
||||
|
||||
// The button view for restoring tabs from last session.
|
||||
private View mRestoreButton;
|
||||
|
||||
// Reference to the View to display when there are no results.
|
||||
private View mEmptyView;
|
||||
|
||||
// Callbacks used for the search and favicon cursor loaders
|
||||
private CursorLoaderCallbacks mCursorLoaderCallbacks;
|
||||
|
||||
// On new tabs listener
|
||||
private OnNewTabsListener mNewTabsListener;
|
||||
|
||||
public static LastTabsPage newInstance() {
|
||||
return new LastTabsPage();
|
||||
}
|
||||
|
||||
public LastTabsPage() {
|
||||
mNewTabsListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
try {
|
||||
mNewTabsListener = (OnNewTabsListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement HomePager.OnNewTabsListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
|
||||
mNewTabsListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.home_last_tabs_page, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
mTitle = (TextView) view.findViewById(R.id.title);
|
||||
if (mTitle != null) {
|
||||
mTitle.setText(R.string.home_last_tabs_title);
|
||||
}
|
||||
|
||||
mList = (ListView) view.findViewById(R.id.list);
|
||||
mList.setTag(HomePager.LIST_TAG_LAST_TABS);
|
||||
|
||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final Cursor c = mAdapter.getCursor();
|
||||
if (c == null || !c.moveToPosition(position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String url = c.getString(c.getColumnIndexOrThrow(Combined.URL));
|
||||
mNewTabsListener.onNewTabs(new String[] { url });
|
||||
}
|
||||
});
|
||||
|
||||
registerForContextMenu(mList);
|
||||
|
||||
mRestoreButton = view.findViewById(R.id.open_all_tabs_button);
|
||||
mRestoreButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
openAllTabs();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mList = null;
|
||||
mTitle = null;
|
||||
mEmptyView = null;
|
||||
mRestoreButton = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
final Activity activity = getActivity();
|
||||
|
||||
// Intialize adapter
|
||||
mAdapter = new LastTabsAdapter(activity);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
// Create callbacks before the initial loader is started
|
||||
mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
|
||||
loadIfVisible();
|
||||
}
|
||||
|
||||
private void updateUiFromCursor(Cursor c) {
|
||||
if (c != null && c.getCount() > 0) {
|
||||
if (mTitle != null) {
|
||||
mTitle.setVisibility(View.VISIBLE);
|
||||
}
|
||||
mRestoreButton.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cursor is empty, so hide the title and set the
|
||||
// empty view if it hasn't been set already.
|
||||
if (mTitle != null) {
|
||||
mTitle.setVisibility(View.GONE);
|
||||
}
|
||||
mRestoreButton.setVisibility(View.GONE);
|
||||
|
||||
if (mEmptyView == null) {
|
||||
// Set empty page view. We delay this so that the empty view won't flash.
|
||||
final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
|
||||
mEmptyView = emptyViewStub.inflate();
|
||||
|
||||
final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
|
||||
emptyIcon.setImageResource(R.drawable.icon_last_tabs_empty);
|
||||
|
||||
final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
|
||||
emptyText.setText(R.string.home_last_tabs_empty);
|
||||
|
||||
mList.setEmptyView(mEmptyView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void load() {
|
||||
getLoaderManager().initLoader(LOADER_ID_LAST_TABS, null, mCursorLoaderCallbacks);
|
||||
}
|
||||
|
||||
private void openAllTabs() {
|
||||
final Cursor c = mAdapter.getCursor();
|
||||
if (c == null || !c.moveToFirst()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String[] urls = new String[c.getCount()];
|
||||
|
||||
do {
|
||||
urls[c.getPosition()] = c.getString(c.getColumnIndexOrThrow(Combined.URL));
|
||||
} while (c.moveToNext());
|
||||
|
||||
mNewTabsListener.onNewTabs(urls);
|
||||
}
|
||||
|
||||
private static class LastTabsCursorLoader extends SimpleCursorLoader {
|
||||
public LastTabsCursorLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
final Context context = getContext();
|
||||
|
||||
final String jsonString = GeckoProfile.get(context).readSessionFile(true);
|
||||
if (jsonString == null) {
|
||||
// No previous session data
|
||||
return null;
|
||||
}
|
||||
|
||||
final MatrixCursor c = new MatrixCursor(new String[] { Combined._ID,
|
||||
Combined.URL,
|
||||
Combined.TITLE });
|
||||
|
||||
new SessionParser() {
|
||||
@Override
|
||||
public void onTabRead(SessionTab tab) {
|
||||
final String url = tab.getUrl();
|
||||
|
||||
// Don't show last tabs for about:home
|
||||
if (url.equals("about:home")) {
|
||||
return;
|
||||
}
|
||||
|
||||
final RowBuilder row = c.newRow();
|
||||
row.add(-1);
|
||||
row.add(url);
|
||||
|
||||
final String title = tab.getTitle();
|
||||
row.add(title);
|
||||
}
|
||||
}.parse(jsonString);
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
private static class LastTabsAdapter extends CursorAdapter {
|
||||
public LastTabsAdapter(Context context) {
|
||||
super(context, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
((TwoLinePageRow) view).updateFromCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return LayoutInflater.from(context).inflate(R.layout.home_item_row, parent, false);
|
||||
}
|
||||
}
|
||||
|
||||
private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
|
||||
public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
|
||||
super(context, loaderManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
if (id == LOADER_ID_LAST_TABS) {
|
||||
return new LastTabsCursorLoader(getActivity());
|
||||
} else {
|
||||
return super.onCreateLoader(id, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
||||
if (loader.getId() == LOADER_ID_LAST_TABS) {
|
||||
mAdapter.swapCursor(c);
|
||||
updateUiFromCursor(c);
|
||||
loadFavicons(c);
|
||||
} else {
|
||||
super.onLoadFinished(loader, c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
if (loader.getId() == LOADER_ID_LAST_TABS) {
|
||||
mAdapter.swapCursor(null);
|
||||
} else {
|
||||
super.onLoaderReset(loader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFaviconsLoaded() {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,402 @@
|
|||
/* -*- 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.BrowserDB;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.home.TwoLinePageRow;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewStub;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Fragment that displays recent history in a ListView.
|
||||
*/
|
||||
public class MostRecentPage extends HomeFragment {
|
||||
// Logging tag name
|
||||
private static final String LOGTAG = "GeckoMostRecentPage";
|
||||
|
||||
// Cursor loader ID for history query
|
||||
private static final int LOADER_ID_HISTORY = 0;
|
||||
|
||||
// Adapter for the list of search results
|
||||
private MostRecentAdapter mAdapter;
|
||||
|
||||
// The view shown by the fragment.
|
||||
private ListView mList;
|
||||
|
||||
// The title for this HomeFragment page.
|
||||
private TextView mTitle;
|
||||
|
||||
// Reference to the View to display when there are no results.
|
||||
private View mEmptyView;
|
||||
|
||||
// Callbacks used for the search and favicon cursor loaders
|
||||
private CursorLoaderCallbacks mCursorLoaderCallbacks;
|
||||
|
||||
// On URL open listener
|
||||
private OnUrlOpenListener mUrlOpenListener;
|
||||
|
||||
public static MostRecentPage newInstance() {
|
||||
return new MostRecentPage();
|
||||
}
|
||||
|
||||
public MostRecentPage() {
|
||||
mUrlOpenListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
try {
|
||||
mUrlOpenListener = (OnUrlOpenListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement HomePager.OnUrlOpenListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mUrlOpenListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.home_most_recent_page, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
mTitle = (TextView) view.findViewById(R.id.title);
|
||||
if (mTitle != null) {
|
||||
mTitle.setText(R.string.home_most_recent_title);
|
||||
}
|
||||
|
||||
mList = (ListView) view.findViewById(R.id.list);
|
||||
mList.setTag(HomePager.LIST_TAG_MOST_RECENT);
|
||||
|
||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
position -= mAdapter.getMostRecentSectionsCountBefore(position);
|
||||
final Cursor c = mAdapter.getCursor(position);
|
||||
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
||||
|
||||
// This item is a TwoLinePageRow, so we allow switch-to-tab.
|
||||
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
|
||||
}
|
||||
});
|
||||
|
||||
registerForContextMenu(mList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mList = null;
|
||||
mTitle = null;
|
||||
mEmptyView = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
final Activity activity = getActivity();
|
||||
|
||||
// Intialize adapter
|
||||
mAdapter = new MostRecentAdapter(activity);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
// Create callbacks before the initial loader is started
|
||||
mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
|
||||
loadIfVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void load() {
|
||||
getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
|
||||
}
|
||||
|
||||
private static class MostRecentCursorLoader extends SimpleCursorLoader {
|
||||
// Max number of history results
|
||||
private static final int HISTORY_LIMIT = 100;
|
||||
|
||||
public MostRecentCursorLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
final ContentResolver cr = getContext().getContentResolver();
|
||||
return BrowserDB.getRecentHistory(cr, HISTORY_LIMIT);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUiFromCursor(Cursor c) {
|
||||
if (c != null && c.getCount() > 0) {
|
||||
if (mTitle != null) {
|
||||
mTitle.setVisibility(View.VISIBLE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Cursor is empty, so hide the title and set the
|
||||
// empty view if it hasn't been set already.
|
||||
if (mTitle != null) {
|
||||
mTitle.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (mEmptyView == null) {
|
||||
// Set empty page view. We delay this so that the empty view won't flash.
|
||||
final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
|
||||
mEmptyView = emptyViewStub.inflate();
|
||||
|
||||
final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
|
||||
emptyIcon.setImageResource(R.drawable.icon_most_recent_empty);
|
||||
|
||||
final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
|
||||
emptyText.setText(R.string.home_most_recent_empty);
|
||||
|
||||
mList.setEmptyView(mEmptyView);
|
||||
}
|
||||
}
|
||||
|
||||
private static class MostRecentAdapter extends MultiTypeCursorAdapter {
|
||||
private static final int ROW_HEADER = 0;
|
||||
private static final int ROW_STANDARD = 1;
|
||||
|
||||
private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
|
||||
private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
|
||||
|
||||
// For the time sections in history
|
||||
private static final long MS_PER_DAY = 86400000;
|
||||
private static final long MS_PER_WEEK = MS_PER_DAY * 7;
|
||||
|
||||
// The time ranges for each section
|
||||
private static enum MostRecentSection {
|
||||
TODAY,
|
||||
YESTERDAY,
|
||||
WEEK,
|
||||
OLDER
|
||||
};
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
// Maps headers in the list with their respective sections
|
||||
private final SparseArray<MostRecentSection> mMostRecentSections;
|
||||
|
||||
public MostRecentAdapter(Context context) {
|
||||
super(context, null, VIEW_TYPES, LAYOUT_TYPES);
|
||||
|
||||
mContext = context;
|
||||
|
||||
// Initialize map of history sections
|
||||
mMostRecentSections = new SparseArray<MostRecentSection>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
final int type = getItemViewType(position);
|
||||
|
||||
// Header items are not in the cursor
|
||||
if (type == ROW_HEADER) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return super.getItem(position - getMostRecentSectionsCountBefore(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (mMostRecentSections.get(position) != null) {
|
||||
return ROW_HEADER;
|
||||
}
|
||||
|
||||
return ROW_STANDARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return (getItemViewType(position) == ROW_STANDARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
// Add the history section headers to the number of reported results.
|
||||
return super.getCount() + mMostRecentSections.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor cursor) {
|
||||
Cursor oldCursor = super.swapCursor(cursor);
|
||||
loadMostRecentSections(cursor);
|
||||
return oldCursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, int position) {
|
||||
final int type = getItemViewType(position);
|
||||
|
||||
if (type == ROW_HEADER) {
|
||||
final MostRecentSection section = mMostRecentSections.get(position);
|
||||
final TextView row = (TextView) view;
|
||||
row.setText(getMostRecentSectionTitle(section));
|
||||
} else {
|
||||
// Account for the most recent section headers
|
||||
position -= getMostRecentSectionsCountBefore(position);
|
||||
final Cursor c = getCursor(position);
|
||||
final TwoLinePageRow row = (TwoLinePageRow) view;
|
||||
row.updateFromCursor(c);
|
||||
}
|
||||
}
|
||||
|
||||
private String getMostRecentSectionTitle(MostRecentSection section) {
|
||||
switch (section) {
|
||||
case TODAY:
|
||||
return mContext.getString(R.string.history_today_section);
|
||||
case YESTERDAY:
|
||||
return mContext.getString(R.string.history_yesterday_section);
|
||||
case WEEK:
|
||||
return mContext.getString(R.string.history_week_section);
|
||||
case OLDER:
|
||||
return mContext.getString(R.string.history_older_section);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Unrecognized history section");
|
||||
}
|
||||
|
||||
private int getMostRecentSectionsCountBefore(int position) {
|
||||
// Account for the number headers before the given position
|
||||
int sectionsBefore = 0;
|
||||
|
||||
final int historySectionsCount = mMostRecentSections.size();
|
||||
for (int i = 0; i < historySectionsCount; i++) {
|
||||
final int sectionPosition = mMostRecentSections.keyAt(i);
|
||||
if (sectionPosition > position) {
|
||||
break;
|
||||
}
|
||||
|
||||
sectionsBefore++;
|
||||
}
|
||||
|
||||
return sectionsBefore;
|
||||
}
|
||||
|
||||
private static MostRecentSection getMostRecentSectionForTime(long from, long time) {
|
||||
long delta = from - time;
|
||||
|
||||
if (delta < 0) {
|
||||
return MostRecentSection.TODAY;
|
||||
}
|
||||
|
||||
if (delta < MS_PER_DAY) {
|
||||
return MostRecentSection.YESTERDAY;
|
||||
}
|
||||
|
||||
if (delta < MS_PER_WEEK) {
|
||||
return MostRecentSection.WEEK;
|
||||
}
|
||||
|
||||
return MostRecentSection.OLDER;
|
||||
}
|
||||
|
||||
private void loadMostRecentSections(Cursor c) {
|
||||
if (c == null || !c.moveToFirst()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any history sections that may have been loaded before.
|
||||
mMostRecentSections.clear();
|
||||
|
||||
final Date now = new Date();
|
||||
now.setHours(0);
|
||||
now.setMinutes(0);
|
||||
now.setSeconds(0);
|
||||
|
||||
final long today = now.getTime();
|
||||
MostRecentSection section = null;
|
||||
|
||||
do {
|
||||
final int position = c.getPosition();
|
||||
final long time = c.getLong(c.getColumnIndexOrThrow(URLColumns.DATE_LAST_VISITED));
|
||||
final MostRecentSection itemSection = MostRecentAdapter.getMostRecentSectionForTime(today, time);
|
||||
|
||||
if (section != itemSection) {
|
||||
section = itemSection;
|
||||
mMostRecentSections.append(position + mMostRecentSections.size(), section);
|
||||
}
|
||||
|
||||
// Reached the last section, no need to continue
|
||||
if (section == MostRecentSection.OLDER) {
|
||||
break;
|
||||
}
|
||||
} while (c.moveToNext());
|
||||
}
|
||||
}
|
||||
|
||||
private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
|
||||
public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
|
||||
super(context, loaderManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
if (id == LOADER_ID_HISTORY) {
|
||||
return new MostRecentCursorLoader(getActivity());
|
||||
} else {
|
||||
return super.onCreateLoader(id, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
||||
if (loader.getId() == LOADER_ID_HISTORY) {
|
||||
mAdapter.swapCursor(c);
|
||||
updateUiFromCursor(c);
|
||||
loadFavicons(c);
|
||||
} else {
|
||||
super.onLoadFinished(loader, c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
if (loader.getId() == LOADER_ID_HISTORY) {
|
||||
mAdapter.swapCursor(null);
|
||||
} else {
|
||||
super.onLoaderReset(loader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFaviconsLoaded() {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
/* -*- 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.BrowserDB;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewStub;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Fragment that displays frecency search results in a ListView.
|
||||
*/
|
||||
public class MostVisitedPage extends HomeFragment {
|
||||
// Logging tag name
|
||||
private static final String LOGTAG = "GeckoMostVisitedPage";
|
||||
|
||||
// Cursor loader ID for search query
|
||||
private static final int LOADER_ID_FRECENCY = 0;
|
||||
|
||||
// Adapter for the list of search results
|
||||
private VisitedAdapter mAdapter;
|
||||
|
||||
// The view shown by the fragment.
|
||||
private ListView mList;
|
||||
|
||||
// The title for this HomeFragment page.
|
||||
private TextView mTitle;
|
||||
|
||||
// Reference to the View to display when there are no results.
|
||||
private View mEmptyView;
|
||||
|
||||
// Callbacks used for the search and favicon cursor loaders
|
||||
private CursorLoaderCallbacks mCursorLoaderCallbacks;
|
||||
|
||||
// On URL open listener
|
||||
private OnUrlOpenListener mUrlOpenListener;
|
||||
|
||||
public static MostVisitedPage newInstance() {
|
||||
return new MostVisitedPage();
|
||||
}
|
||||
|
||||
public MostVisitedPage() {
|
||||
mUrlOpenListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
try {
|
||||
mUrlOpenListener = (OnUrlOpenListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement HomePager.OnUrlOpenListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
|
||||
mUrlOpenListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.home_most_visited_page, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
mTitle = (TextView) view.findViewById(R.id.title);
|
||||
if (mTitle != null) {
|
||||
mTitle.setText(R.string.home_most_visited_title);
|
||||
}
|
||||
|
||||
mList = (HomeListView) view.findViewById(R.id.list);
|
||||
mList.setTag(HomePager.LIST_TAG_MOST_VISITED);
|
||||
|
||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final Cursor c = mAdapter.getCursor();
|
||||
if (c == null || !c.moveToPosition(position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
||||
|
||||
// This item is a TwoLinePageRow, so we allow switch-to-tab.
|
||||
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
|
||||
}
|
||||
});
|
||||
|
||||
registerForContextMenu(mList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mList = null;
|
||||
mTitle = null;
|
||||
mEmptyView = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
final Activity activity = getActivity();
|
||||
|
||||
// Intialize the search adapter
|
||||
mAdapter = new VisitedAdapter(activity, null);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
// Create callbacks before the initial loader is started
|
||||
mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
|
||||
loadIfVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void load() {
|
||||
getLoaderManager().initLoader(LOADER_ID_FRECENCY, null, mCursorLoaderCallbacks);
|
||||
}
|
||||
|
||||
private void updateUiFromCursor(Cursor c) {
|
||||
if (c != null && c.getCount() > 0) {
|
||||
if (mTitle != null) {
|
||||
mTitle.setVisibility(View.VISIBLE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Cursor is empty, so hide the title and set the
|
||||
// empty view if it hasn't been set already.
|
||||
if (mTitle != null) {
|
||||
mTitle.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (mEmptyView == null) {
|
||||
// Set empty page view. We delay this so that the empty view won't flash.
|
||||
ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
|
||||
mEmptyView = emptyViewStub.inflate();
|
||||
|
||||
final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
|
||||
emptyIcon.setImageResource(R.drawable.icon_most_visited_empty);
|
||||
|
||||
final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
|
||||
emptyText.setText(R.string.home_most_visited_empty);
|
||||
|
||||
mList.setEmptyView(mEmptyView);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FrecencyCursorLoader extends SimpleCursorLoader {
|
||||
// Max number of search results
|
||||
private static final int SEARCH_LIMIT = 50;
|
||||
|
||||
public FrecencyCursorLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
final ContentResolver cr = getContext().getContentResolver();
|
||||
return BrowserDB.filter(cr, "", SEARCH_LIMIT);
|
||||
}
|
||||
}
|
||||
|
||||
private class VisitedAdapter extends CursorAdapter {
|
||||
public VisitedAdapter(Context context, Cursor cursor) {
|
||||
super(context, cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
final TwoLinePageRow row = (TwoLinePageRow) view;
|
||||
row.updateFromCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return LayoutInflater.from(parent.getContext()).inflate(R.layout.home_item_row, parent, false);
|
||||
}
|
||||
}
|
||||
|
||||
private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
|
||||
public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
|
||||
super(context, loaderManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
if (id == LOADER_ID_FRECENCY) {
|
||||
return new FrecencyCursorLoader(getActivity());
|
||||
} else {
|
||||
return super.onCreateLoader(id, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
||||
if (loader.getId() == LOADER_ID_FRECENCY) {
|
||||
mAdapter.swapCursor(c);
|
||||
updateUiFromCursor(c);
|
||||
loadFavicons(c);
|
||||
} else {
|
||||
super.onLoadFinished(loader, c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
if (loader.getId() == LOADER_ID_FRECENCY) {
|
||||
mAdapter.swapCursor(null);
|
||||
} else {
|
||||
super.onLoaderReset(loader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFaviconsLoaded() {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/* -*- 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 android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* MultiTypeCursorAdapter wraps a cursor and any meta data associated with it.
|
||||
* A set of view types (corresponding to the cursor and its meta data)
|
||||
* are mapped to a set of layouts.
|
||||
*/
|
||||
abstract class MultiTypeCursorAdapter extends CursorAdapter {
|
||||
private final int[] mViewTypes;
|
||||
private final int[] mLayouts;
|
||||
|
||||
// Bind the view for the given position.
|
||||
abstract public void bindView(View view, Context context, int position);
|
||||
|
||||
public MultiTypeCursorAdapter(Context context, Cursor cursor, int[] viewTypes, int[] layouts) {
|
||||
super(context, cursor);
|
||||
|
||||
if (viewTypes.length != layouts.length) {
|
||||
throw new IllegalStateException("The view types and the layouts should be of same size");
|
||||
}
|
||||
|
||||
mViewTypes = viewTypes;
|
||||
mLayouts = layouts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getViewTypeCount() {
|
||||
return mViewTypes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Cursor for the given position.
|
||||
*/
|
||||
public final Cursor getCursor(int position) {
|
||||
final Cursor cursor = getCursor();
|
||||
if (cursor == null || !cursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("Couldn't move cursor to position " + position);
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final View getView(int position, View convertView, ViewGroup parent) {
|
||||
final Context context = parent.getContext();
|
||||
if (convertView == null) {
|
||||
convertView = newView(context, position, parent);
|
||||
}
|
||||
|
||||
bindView(convertView, context, position);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void bindView(View view, Context context, Cursor cursor) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public final View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate a new view from a set of view types and layouts based on the position.
|
||||
*
|
||||
* @param context Context for inflating the view.
|
||||
* @param position Position of the view.
|
||||
* @param parent Parent view group that will hold this view.
|
||||
*/
|
||||
private View newView(Context context, int position, ViewGroup parent) {
|
||||
final int type = getItemViewType(position);
|
||||
final int count = mViewTypes.length;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (mViewTypes[i] == type) {
|
||||
return LayoutInflater.from(context).inflate(mLayouts[i], parent, false);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
/* -*- 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.BrowserDB.URLColumns;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
|
||||
/**
|
||||
* Dialog fragment that displays frecency search results, for pinning as a bookmark, in a ListView.
|
||||
*/
|
||||
class PinBookmarkDialog extends DialogFragment {
|
||||
// Listener for url selection
|
||||
public static interface OnBookmarkSelectedListener {
|
||||
public void onBookmarkSelected(String url, String title);
|
||||
}
|
||||
|
||||
// Cursor loader ID for search query
|
||||
private static final int LOADER_ID_SEARCH = 0;
|
||||
|
||||
// Cursor loader ID for favicons query
|
||||
private static final int LOADER_ID_FAVICONS = 1;
|
||||
|
||||
// Holds the current search term to use in the query
|
||||
private String mSearchTerm;
|
||||
|
||||
// Adapter for the list of search results
|
||||
private SearchAdapter mAdapter;
|
||||
|
||||
// Search entry
|
||||
private EditText mSearch;
|
||||
|
||||
// Search results
|
||||
private ListView mList;
|
||||
|
||||
// Callbacks used for the search and favicon cursor loaders
|
||||
private CursorLoaderCallbacks mLoaderCallbacks;
|
||||
|
||||
// Bookmark selected listener
|
||||
private OnBookmarkSelectedListener mOnBookmarkSelectedListener;
|
||||
|
||||
public static PinBookmarkDialog newInstance() {
|
||||
return new PinBookmarkDialog();
|
||||
}
|
||||
|
||||
private PinBookmarkDialog() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setStyle(DialogFragment.STYLE_NO_TITLE, android.R.style.Theme_Holo_Light_Dialog);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
// All list views are styled to look the same with a global activity theme.
|
||||
// If the style of the list changes, inflate it from an XML.
|
||||
return inflater.inflate(R.layout.pin_bookmark_dialog, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
mSearch = (EditText) view.findViewById(R.id.search);
|
||||
mSearch.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
filter(mSearch.getText().toString());
|
||||
}
|
||||
});
|
||||
|
||||
mList = (HomeListView) view.findViewById(R.id.list);
|
||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (mOnBookmarkSelectedListener != null) {
|
||||
final Cursor c = mAdapter.getCursor();
|
||||
if (c == null || !c.moveToPosition(position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
||||
final String title = c.getString(c.getColumnIndexOrThrow(URLColumns.TITLE));
|
||||
mOnBookmarkSelectedListener.onBookmarkSelected(url, title);
|
||||
}
|
||||
|
||||
// Dismiss the fragment and the dialog.
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
final Activity activity = getActivity();
|
||||
final LoaderManager manager = getLoaderManager();
|
||||
|
||||
// Initialize the search adapter
|
||||
mAdapter = new SearchAdapter(activity);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
// Create callbacks before the initial loader is started
|
||||
mLoaderCallbacks = new CursorLoaderCallbacks(activity, manager);
|
||||
|
||||
// Reconnect to the loader only if present
|
||||
manager.initLoader(LOADER_ID_SEARCH, null, mLoaderCallbacks);
|
||||
|
||||
// Default filter.
|
||||
filter("");
|
||||
}
|
||||
|
||||
private void filter(String searchTerm) {
|
||||
if (!TextUtils.isEmpty(searchTerm) &&
|
||||
TextUtils.equals(mSearchTerm, searchTerm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSearchTerm = searchTerm;
|
||||
|
||||
// Restart loaders with the new search term
|
||||
SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH, mLoaderCallbacks, mSearchTerm);
|
||||
}
|
||||
|
||||
public void setOnBookmarkSelectedListener(OnBookmarkSelectedListener listener) {
|
||||
mOnBookmarkSelectedListener = listener;
|
||||
}
|
||||
|
||||
private static class SearchAdapter extends CursorAdapter {
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
public SearchAdapter(Context context) {
|
||||
super(context, null);
|
||||
mInflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TwoLinePageRow row = (TwoLinePageRow) view;
|
||||
row.setShowIcons(false);
|
||||
row.updateFromCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return (TwoLinePageRow) mInflater.inflate(R.layout.home_item_row, parent, false);
|
||||
}
|
||||
}
|
||||
|
||||
private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
|
||||
public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
|
||||
super(context, loaderManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
if (id == LOADER_ID_SEARCH) {
|
||||
return SearchLoader.createInstance(getActivity(), args);
|
||||
} else {
|
||||
return super.onCreateLoader(id, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
||||
if (loader.getId() == LOADER_ID_SEARCH) {
|
||||
mAdapter.swapCursor(c);
|
||||
loadFavicons(c);
|
||||
} else {
|
||||
super.onLoadFinished(loader, c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
if (loader.getId() == LOADER_ID_SEARCH) {
|
||||
mAdapter.swapCursor(null);
|
||||
} else {
|
||||
super.onLoaderReset(loader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFaviconsLoaded() {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
/* -*- 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;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.home.TwoLinePageRow;
|
||||
import org.mozilla.gecko.ReaderModeUtils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewStub;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Fragment that displays reading list contents in a ListView.
|
||||
*/
|
||||
public class ReadingListPage extends HomeFragment {
|
||||
// Cursor loader ID for reading list
|
||||
private static final int LOADER_ID_READING_LIST = 0;
|
||||
|
||||
// Adapter for the list of reading list items
|
||||
private ReadingListAdapter mAdapter;
|
||||
|
||||
// The view shown by the fragment
|
||||
private ListView mList;
|
||||
|
||||
// Reference to the View to display when there are no results.
|
||||
private View mEmptyView;
|
||||
|
||||
// Reference to top view.
|
||||
private View mTopView;
|
||||
|
||||
// Callbacks used for the reading list and favicon cursor loaders
|
||||
private CursorLoaderCallbacks mCursorLoaderCallbacks;
|
||||
|
||||
// On URL open listener
|
||||
private OnUrlOpenListener mUrlOpenListener;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
try {
|
||||
mUrlOpenListener = (OnUrlOpenListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement HomePager.OnUrlOpenListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
|
||||
mUrlOpenListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.home_reading_list_page, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
mTopView = view;
|
||||
|
||||
mList = (ListView) view.findViewById(R.id.list);
|
||||
mList.setTag(HomePager.LIST_TAG_READING_LIST);
|
||||
|
||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final Cursor c = mAdapter.getCursor();
|
||||
if (c == null || !c.moveToPosition(position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
||||
url = ReaderModeUtils.getAboutReaderForUrl(url, true);
|
||||
|
||||
// This item is a TwoLinePageRow, so we allow switch-to-tab.
|
||||
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
|
||||
}
|
||||
});
|
||||
|
||||
registerForContextMenu(mList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mList = null;
|
||||
mTopView = null;
|
||||
mEmptyView = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
// Detach and reattach the fragment as the layout changes.
|
||||
if (isVisible()) {
|
||||
getFragmentManager().beginTransaction()
|
||||
.detach(this)
|
||||
.attach(this)
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mAdapter = new ReadingListAdapter(getActivity(), null);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
// Create callbacks before the initial loader is started.
|
||||
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
|
||||
loadIfVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void load() {
|
||||
getLoaderManager().initLoader(LOADER_ID_READING_LIST, null, mCursorLoaderCallbacks);
|
||||
}
|
||||
|
||||
private void updateUiFromCursor(Cursor c) {
|
||||
// We delay setting the empty view until the cursor is actually empty.
|
||||
// This avoids image flashing.
|
||||
if ((c == null || c.getCount() == 0) && mEmptyView == null) {
|
||||
final ViewStub emptyViewStub = (ViewStub) mTopView.findViewById(R.id.home_empty_view_stub);
|
||||
mEmptyView = emptyViewStub.inflate();
|
||||
|
||||
final TextView emptyHint = (TextView) mEmptyView.findViewById(R.id.home_empty_hint);
|
||||
String readingListHint = emptyHint.getText().toString();
|
||||
|
||||
// Use an ImageSpan to include the reader icon in the "Tip".
|
||||
int imageSpanIndex = readingListHint.indexOf("%I");
|
||||
if (imageSpanIndex != -1) {
|
||||
final ImageSpan readingListIcon = new ImageSpan(getActivity(), R.drawable.reader_cropped, ImageSpan.ALIGN_BOTTOM);
|
||||
final SpannableStringBuilder hintBuilder = new SpannableStringBuilder(readingListHint);
|
||||
hintBuilder.setSpan(readingListIcon, imageSpanIndex, imageSpanIndex + 2, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
|
||||
emptyHint.setText(hintBuilder, TextView.BufferType.SPANNABLE);
|
||||
}
|
||||
|
||||
mList.setEmptyView(mEmptyView);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cursor loader for the list of reading list items.
|
||||
*/
|
||||
private static class ReadingListLoader extends SimpleCursorLoader {
|
||||
public ReadingListLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
return BrowserDB.getBookmarksInFolder(getContext().getContentResolver(), Bookmarks.FIXED_READING_LIST_ID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cursor adapter for the list of reading list items.
|
||||
*/
|
||||
private class ReadingListAdapter extends CursorAdapter {
|
||||
public ReadingListAdapter(Context context, Cursor cursor) {
|
||||
super(context, cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
final TwoLinePageRow row = (TwoLinePageRow) view;
|
||||
row.updateFromCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return LayoutInflater.from(parent.getContext()).inflate(R.layout.bookmark_item_row, parent, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LoaderCallbacks implementation that interacts with the LoaderManager.
|
||||
*/
|
||||
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
switch(id) {
|
||||
case LOADER_ID_READING_LIST:
|
||||
return new ReadingListLoader(getActivity());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
||||
final int loaderId = loader.getId();
|
||||
switch(loaderId) {
|
||||
case LOADER_ID_READING_LIST:
|
||||
mAdapter.swapCursor(c);
|
||||
break;
|
||||
}
|
||||
|
||||
updateUiFromCursor(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
final int loaderId = loader.getId();
|
||||
switch(loaderId) {
|
||||
case LOADER_ID_READING_LIST:
|
||||
mAdapter.swapCursor(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,13 +3,13 @@
|
|||
* 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;
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SearchEngine {
|
||||
class SearchEngine {
|
||||
public String name;
|
||||
public String identifier;
|
||||
public Bitmap icon;
|
|
@ -3,26 +3,29 @@
|
|||
* 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;
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.AwesomeBarTabs.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.util.GamepadUtils;
|
||||
import org.mozilla.gecko.AnimatedHeightLayout;
|
||||
import org.mozilla.gecko.FlowLayout;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.home.BrowserSearch.OnEditSuggestionListener;
|
||||
import org.mozilla.gecko.home.BrowserSearch.OnSearchListener;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.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;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
class SearchEngineRow extends AnimatedHeightLayout {
|
||||
// Duration for fade-in animation
|
||||
private static final int ANIMATION_DURATION = 250;
|
||||
|
@ -39,9 +42,6 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
|||
// 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;
|
||||
|
@ -49,6 +49,15 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
|||
// On URL open listener
|
||||
private OnUrlOpenListener mUrlOpenListener;
|
||||
|
||||
// On search listener
|
||||
private OnSearchListener mSearchListener;
|
||||
|
||||
// On edit suggestion listener
|
||||
private OnEditSuggestionListener mEditSuggestionListener;
|
||||
|
||||
// Selected suggestion view
|
||||
private int mSelectedView = 0;
|
||||
|
||||
public SearchEngineRow(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
@ -68,12 +77,12 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
|||
// 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);
|
||||
if (v != mUserEnteredView && !StringUtils.isSearchQuery(suggestion, false)) {
|
||||
if (mUrlOpenListener != null) {
|
||||
mUrlOpenListener.onUrlOpen(suggestion, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
|
||||
}
|
||||
} else if (mSearchListener != null) {
|
||||
mSearchListener.onSearch(mSearchEngine.name, suggestion);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -81,9 +90,9 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
|||
mLongClickListener = new OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
if (mUrlOpenListener != null) {
|
||||
if (mEditSuggestionListener != null) {
|
||||
final String suggestion = getSuggestionTextFromView(v);
|
||||
mUrlOpenListener.onEditSuggestion(suggestion);
|
||||
mEditSuggestionListener.onEditSuggestion(suggestion);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -102,6 +111,31 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
|||
mUserEnteredView.setOnClickListener(mClickListener);
|
||||
|
||||
mUserEnteredTextView = (TextView) findViewById(R.id.suggestion_text);
|
||||
|
||||
// Handle clicks on this row that don't happen on individual suggestion views.
|
||||
setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Don't do anything if we are showing suggestions.
|
||||
if (mSearchEngine.suggestions.size() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, perform a search for the user entered term.
|
||||
String searchTerm = getSuggestionTextFromView(mUserEnteredView);
|
||||
if (mSearchListener != null) {
|
||||
mSearchListener.onSearch(mSearchEngine.name, searchTerm);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Intercept long clicks to avoid trying to show a context menu.
|
||||
setOnLongClickListener(new OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getSuggestionTextFromView(View v) {
|
||||
|
@ -122,7 +156,15 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
|||
mUrlOpenListener = listener;
|
||||
}
|
||||
|
||||
public void updateFromSearchEngine(SearchEngine searchEngine, boolean doAnimation) {
|
||||
public void setOnSearchListener(OnSearchListener listener) {
|
||||
mSearchListener = listener;
|
||||
}
|
||||
|
||||
public void setOnEditSuggestionListener(OnEditSuggestionListener listener) {
|
||||
mEditSuggestionListener = listener;
|
||||
}
|
||||
|
||||
public void updateFromSearchEngine(SearchEngine searchEngine, boolean animate) {
|
||||
// Update search engine reference
|
||||
mSearchEngine = searchEngine;
|
||||
|
||||
|
@ -156,7 +198,7 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
|||
final String suggestion = mSearchEngine.suggestions.get(i);
|
||||
setSuggestionOnView(suggestionItem, suggestion);
|
||||
|
||||
if (doAnimation) {
|
||||
if (animate) {
|
||||
AlphaAnimation anim = new AlphaAnimation(0, 1);
|
||||
anim.setDuration(ANIMATION_DURATION);
|
||||
anim.setStartOffset(i * ANIMATION_DURATION);
|
|
@ -0,0 +1,84 @@
|
|||
/* -*- 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.db.BrowserDB;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.text.TextUtils;
|
||||
|
||||
/**
|
||||
* Encapsulates the implementation of the search cursor loader.
|
||||
*/
|
||||
class SearchLoader {
|
||||
// Key for search terms
|
||||
private static final String KEY_SEARCH_TERM = "search_term";
|
||||
|
||||
// Key for performing empty search
|
||||
private static final String KEY_PERFORM_EMPTY_SEARCH = "perform_empty_search";
|
||||
|
||||
private SearchLoader() {
|
||||
}
|
||||
|
||||
public static Loader<Cursor> createInstance(Context context, Bundle args) {
|
||||
if (args != null) {
|
||||
final String searchTerm = args.getString(KEY_SEARCH_TERM);
|
||||
final boolean performEmptySearch = args.getBoolean(KEY_PERFORM_EMPTY_SEARCH, false);
|
||||
return new SearchCursorLoader(context, searchTerm, performEmptySearch);
|
||||
} else {
|
||||
return new SearchCursorLoader(context, "", false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void restart(LoaderManager manager, int loaderId,
|
||||
LoaderCallbacks<Cursor> callbacks, String searchTerm) {
|
||||
restart(manager, loaderId, callbacks, searchTerm, true);
|
||||
}
|
||||
|
||||
public static void restart(LoaderManager manager, int loaderId,
|
||||
LoaderCallbacks<Cursor> callbacks, String searchTerm, boolean performEmptySearch) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(SearchLoader.KEY_SEARCH_TERM, searchTerm);
|
||||
bundle.putBoolean(SearchLoader.KEY_PERFORM_EMPTY_SEARCH, performEmptySearch);
|
||||
manager.restartLoader(loaderId, bundle, callbacks);
|
||||
}
|
||||
|
||||
public static class SearchCursorLoader extends SimpleCursorLoader {
|
||||
// Max number of search results
|
||||
private static final int SEARCH_LIMIT = 100;
|
||||
|
||||
// The target search term associated with the loader
|
||||
private final String mSearchTerm;
|
||||
|
||||
// An empty search on the DB
|
||||
private final boolean mPerformEmptySearch;
|
||||
|
||||
public SearchCursorLoader(Context context, String searchTerm, boolean performEmptySearch) {
|
||||
super(context);
|
||||
mSearchTerm = searchTerm;
|
||||
mPerformEmptySearch = performEmptySearch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
if (!mPerformEmptySearch && TextUtils.isEmpty(mSearchTerm)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return BrowserDB.filter(getContext().getContentResolver(), mSearchTerm, SEARCH_LIMIT);
|
||||
}
|
||||
|
||||
public String getSearchTerm() {
|
||||
return mSearchTerm;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* This is an adapted version of Android's original CursorLoader
|
||||
* without all the ContentProvider-specific bits.
|
||||
*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
|
||||
abstract class SimpleCursorLoader extends AsyncTaskLoader<Cursor> {
|
||||
final ForceLoadContentObserver mObserver;
|
||||
Cursor mCursor;
|
||||
|
||||
public SimpleCursorLoader(Context context) {
|
||||
super(context);
|
||||
mObserver = new ForceLoadContentObserver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the target cursor for this loader. This method is called
|
||||
* on a worker thread.
|
||||
*/
|
||||
protected abstract Cursor loadCursor();
|
||||
|
||||
/* Runs on a worker thread */
|
||||
@Override
|
||||
public Cursor loadInBackground() {
|
||||
Cursor cursor = loadCursor();
|
||||
|
||||
if (cursor != null) {
|
||||
// Ensure the cursor window is filled
|
||||
cursor.getCount();
|
||||
cursor.registerContentObserver(mObserver);
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/* Runs on the UI thread */
|
||||
@Override
|
||||
public void deliverResult(Cursor cursor) {
|
||||
if (isReset()) {
|
||||
// An async query came in while the loader is stopped
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Cursor oldCursor = mCursor;
|
||||
mCursor = cursor;
|
||||
|
||||
if (isStarted()) {
|
||||
super.deliverResult(cursor);
|
||||
}
|
||||
|
||||
if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
|
||||
oldCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an asynchronous load of the list data. When the result is ready the callbacks
|
||||
* will be called on the UI thread. If a previous load has been completed and is still valid
|
||||
* the result may be passed to the callbacks immediately.
|
||||
*
|
||||
* Must be called from the UI thread
|
||||
*/
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mCursor != null) {
|
||||
deliverResult(mCursor);
|
||||
}
|
||||
|
||||
if (takeContentChanged() || mCursor == null) {
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called from the UI thread
|
||||
*/
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
// Attempt to cancel the current load task if possible.
|
||||
cancelLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(Cursor cursor) {
|
||||
if (cursor != null && !cursor.isClosed()) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
super.onReset();
|
||||
|
||||
// Ensure the loader is stopped
|
||||
onStopLoading();
|
||||
|
||||
if (mCursor != null && !mCursor.isClosed()) {
|
||||
mCursor.close();
|
||||
}
|
||||
|
||||
mCursor = null;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,9 @@
|
|||
* 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;
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
|
@ -23,7 +25,7 @@ import java.util.ArrayList;
|
|||
/**
|
||||
* Use network-based search suggestions.
|
||||
*/
|
||||
public class SuggestClient {
|
||||
class SuggestClient {
|
||||
private static final String LOGTAG = "GeckoSuggestClient";
|
||||
private static final String USER_AGENT = GeckoAppShell.getGeckoInterface().getDefaultUAString();
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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 android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.Rect;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.home.HomePager;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
public class TabMenuStrip extends LinearLayout
|
||||
implements HomePager.Decor,
|
||||
View.OnFocusChangeListener {
|
||||
private static final String LOGTAG = "GeckoTabMenuStrip";
|
||||
|
||||
private HomePager.OnTitleClickListener mOnTitleClickListener;
|
||||
private Drawable mStrip;
|
||||
private View mSelectedView;
|
||||
|
||||
// Data associated with the scrolling of the strip drawable.
|
||||
private View toTab;
|
||||
private View fromTab;
|
||||
private float progress;
|
||||
|
||||
// This variable is used to predict the direction of scroll.
|
||||
private float mPrevProgress;
|
||||
|
||||
public TabMenuStrip(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip);
|
||||
final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1);
|
||||
a.recycle();
|
||||
|
||||
if (stripResId != -1) {
|
||||
mStrip = getResources().getDrawable(stripResId);
|
||||
}
|
||||
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddPagerView(String title) {
|
||||
final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false);
|
||||
button.setText(title.toUpperCase());
|
||||
|
||||
addView(button);
|
||||
button.setOnClickListener(new ViewClickListener(getChildCount() - 1));
|
||||
button.setOnFocusChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllPagerViews() {
|
||||
removeAllViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(final int position) {
|
||||
mSelectedView = getChildAt(position);
|
||||
|
||||
// Callback to measure and draw the strip after the view is visible.
|
||||
ViewTreeObserver vto = mSelectedView.getViewTreeObserver();
|
||||
if (vto.isAlive()) {
|
||||
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
mSelectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
|
||||
if (mStrip != null) {
|
||||
mStrip.setBounds(mSelectedView.getLeft(),
|
||||
mSelectedView.getTop(),
|
||||
mSelectedView.getRight(),
|
||||
mSelectedView.getBottom());
|
||||
}
|
||||
|
||||
mPrevProgress = position;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Page scroll animates the drawable and it's bounds from the previous to next child view.
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
if (mStrip == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setScrollingData(position, positionOffset);
|
||||
|
||||
final int fromTabLeft = fromTab.getLeft();
|
||||
final int fromTabRight = fromTab.getRight();
|
||||
|
||||
final int toTabLeft = toTab.getLeft();
|
||||
final int toTabRight = toTab.getRight();
|
||||
|
||||
mStrip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)),
|
||||
0,
|
||||
(int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
|
||||
getHeight());
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/*
|
||||
* position + positionOffset goes from 0 to 2 as we scroll from page 1 to 3.
|
||||
* Normalized progress is relative to the the direction the page is being scrolled towards.
|
||||
* For this, we maintain direction of scroll with a state, and the child view we are moving towards and away from.
|
||||
*/
|
||||
private void setScrollingData(int position, float positionOffset) {
|
||||
if (position >= getChildCount() - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
final float currProgress = position + positionOffset;
|
||||
|
||||
if (mPrevProgress > currProgress) {
|
||||
toTab = getChildAt(position);
|
||||
fromTab = getChildAt(position + 1);
|
||||
progress = 1 - positionOffset;
|
||||
} else {
|
||||
toTab = getChildAt(position + 1);
|
||||
fromTab = getChildAt(position);
|
||||
progress = positionOffset;
|
||||
}
|
||||
|
||||
mPrevProgress = currProgress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (mStrip != null) {
|
||||
mStrip.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (v == this && hasFocus && getChildCount() > 0) {
|
||||
mSelectedView.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
final int numTabs = getChildCount();
|
||||
|
||||
while (i < numTabs) {
|
||||
View view = getChildAt(i);
|
||||
if (view == v) {
|
||||
view.requestFocus();
|
||||
if (isShown()) {
|
||||
// A view is focused so send an event to announce the menu strip state.
|
||||
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnTitleClickListener(HomePager.OnTitleClickListener onTitleClickListener) {
|
||||
mOnTitleClickListener = onTitleClickListener;
|
||||
}
|
||||
|
||||
private class ViewClickListener implements OnClickListener {
|
||||
private final int mIndex;
|
||||
|
||||
public ViewClickListener(int index) {
|
||||
mIndex = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (mOnTitleClickListener != null) {
|
||||
mOnTitleClickListener.onTitleClicked(mIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/* -*- 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 android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.PathShape;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ImageView.ScaleType;
|
||||
|
||||
/**
|
||||
* A view that displays the thumbnail and the title/url for a bookmark.
|
||||
* If the title/url is longer than the width of the view, they are faded out.
|
||||
* If there is no valid url, a default string is shown at 50% opacity.
|
||||
* This is denoted by the empty state.
|
||||
*/
|
||||
public class TopBookmarkItemView extends RelativeLayout {
|
||||
private static final String LOGTAG = "GeckoTopBookmarkItemView";
|
||||
|
||||
// Empty state, to denote there is no valid url.
|
||||
private static final int[] STATE_EMPTY = { android.R.attr.state_empty };
|
||||
|
||||
// A Pin Drawable to denote pinned sites.
|
||||
private static Drawable sPinDrawable = null;
|
||||
|
||||
// Child views.
|
||||
private final TextView mTitleView;
|
||||
private final ImageView mThumbnailView;
|
||||
private final ImageView mPinView;
|
||||
|
||||
// Data backing this view.
|
||||
private String mTitle;
|
||||
private String mUrl;
|
||||
|
||||
// Pinned state.
|
||||
private boolean mIsPinned = false;
|
||||
|
||||
// Empty state.
|
||||
private boolean mIsEmpty = true;
|
||||
|
||||
public TopBookmarkItemView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TopBookmarkItemView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.topBookmarkItemViewStyle);
|
||||
}
|
||||
|
||||
public TopBookmarkItemView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
LayoutInflater.from(context).inflate(R.layout.top_bookmark_item_view, this);
|
||||
|
||||
mTitleView = (TextView) findViewById(R.id.title);
|
||||
mThumbnailView = (ImageView) findViewById(R.id.thumbnail);
|
||||
mPinView = (ImageView) findViewById(R.id.pin);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
|
||||
if (mIsEmpty) {
|
||||
mergeDrawableStates(drawableState, STATE_EMPTY);
|
||||
}
|
||||
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The title shown by this view.
|
||||
*/
|
||||
public String getTitle() {
|
||||
return (!TextUtils.isEmpty(mTitle) ? mTitle : mUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The url shown by this view.
|
||||
*/
|
||||
public String getUrl() {
|
||||
return mUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true, if this view is pinned, false otherwise.
|
||||
*/
|
||||
public boolean isPinned() {
|
||||
return mIsPinned;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param title The title for this view.
|
||||
*/
|
||||
public void setTitle(String title) {
|
||||
if (mTitle != null && mTitle.equals(title)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mTitle = title;
|
||||
updateTitleView();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url The url for this view.
|
||||
*/
|
||||
public void setUrl(String url) {
|
||||
if (mUrl != null && mUrl.equals(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mUrl = url;
|
||||
updateTitleView();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pinned The pinned state of this view.
|
||||
*/
|
||||
public void setPinned(boolean pinned) {
|
||||
mIsPinned = pinned;
|
||||
mPinView.setBackgroundDrawable(pinned ? getPinDrawable() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the thumbnail from a resource.
|
||||
*
|
||||
* @param resId Resource ID of the drawable to show.
|
||||
*/
|
||||
public void displayThumbnail(int resId) {
|
||||
mThumbnailView.setScaleType(ScaleType.CENTER);
|
||||
mThumbnailView.setImageResource(resId);
|
||||
mThumbnailView.setBackgroundColor(0x0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the thumbnail from a bitmap.
|
||||
*
|
||||
* @param thumbnail The bitmap to show as thumbnail.
|
||||
*/
|
||||
public void displayThumbnail(Bitmap thumbnail) {
|
||||
if (thumbnail == null) {
|
||||
// Show a favicon based view instead.
|
||||
displayThumbnail(R.drawable.favicon);
|
||||
return;
|
||||
}
|
||||
|
||||
mThumbnailView.setScaleType(ScaleType.CENTER_CROP);
|
||||
mThumbnailView.setImageBitmap(thumbnail);
|
||||
mThumbnailView.setBackgroundDrawable(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the thumbnail from a favicon.
|
||||
*
|
||||
* @param favicon The favicon to show as thumbnail.
|
||||
*/
|
||||
public void displayFavicon(Bitmap favicon) {
|
||||
if (favicon == null) {
|
||||
// Should show default favicon.
|
||||
displayThumbnail(R.drawable.favicon);
|
||||
return;
|
||||
}
|
||||
|
||||
mThumbnailView.setScaleType(ScaleType.CENTER);
|
||||
mThumbnailView.setImageBitmap(favicon);
|
||||
mThumbnailView.setBackgroundColor(Favicons.getInstance().getFaviconColor(favicon, mUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the title shown by this view. If both title and url
|
||||
* are empty, mark the state as STATE_EMPTY and show a default text.
|
||||
*/
|
||||
private void updateTitleView() {
|
||||
String title = getTitle();
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
mTitleView.setText(title);
|
||||
mIsEmpty = false;
|
||||
} else {
|
||||
mTitleView.setText(R.string.bookmark_add);
|
||||
mIsEmpty = true;
|
||||
}
|
||||
|
||||
// Refresh for state change.
|
||||
refreshDrawableState();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Drawable to be used as a pin.
|
||||
*/
|
||||
private Drawable getPinDrawable() {
|
||||
if (sPinDrawable == null) {
|
||||
int size = getResources().getDimensionPixelSize(R.dimen.top_bookmark_pinsize);
|
||||
|
||||
// Draw a little triangle in the upper right corner.
|
||||
Path path = new Path();
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(size, 0);
|
||||
path.lineTo(size, size);
|
||||
path.close();
|
||||
|
||||
sPinDrawable = new ShapeDrawable(new PathShape(path, size, size));
|
||||
Paint p = ((ShapeDrawable) sPinDrawable).getPaint();
|
||||
p.setColor(getResources().getColor(R.color.top_bookmark_pin));
|
||||
}
|
||||
|
||||
return sPinDrawable;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/* -*- 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.BrowserDB.TopSitesCursorWrapper;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A cursor adapter holding the pinned and top bookmarks.
|
||||
*/
|
||||
public class TopBookmarksAdapter extends CursorAdapter {
|
||||
// Cache to store the thumbnails.
|
||||
private Map<String, Thumbnail> mThumbnails;
|
||||
|
||||
/**
|
||||
* Class to hold the bitmap of cached thumbnails/favicons.
|
||||
*/
|
||||
public static class Thumbnail {
|
||||
// Thumbnail or favicon.
|
||||
private final boolean isThumbnail;
|
||||
|
||||
// Bitmap of thumbnail/favicon.
|
||||
private final Bitmap bitmap;
|
||||
|
||||
public Thumbnail(Bitmap bitmap, boolean isThumbnail) {
|
||||
this.bitmap = bitmap;
|
||||
this.isThumbnail = isThumbnail;
|
||||
}
|
||||
}
|
||||
|
||||
public TopBookmarksAdapter(Context context, Cursor cursor) {
|
||||
super(context, cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onContentChanged() {
|
||||
// Don't do anything. We don't want to regenerate every time
|
||||
// our database is updated.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the thumbnails returned by the db.
|
||||
*
|
||||
* @param thumbnails A map of urls and their thumbnail bitmaps.
|
||||
*/
|
||||
public void updateThumbnails(Map<String, Thumbnail> thumbnails) {
|
||||
mThumbnails = thumbnails;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void bindView(View bindView, Context context, Cursor cursor) {
|
||||
String url = "";
|
||||
String title = "";
|
||||
boolean pinned = false;
|
||||
|
||||
// Cursor is already moved to required position.
|
||||
if (!cursor.isAfterLast()) {
|
||||
url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
|
||||
title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
|
||||
pinned = ((TopSitesCursorWrapper) cursor).isPinned();
|
||||
}
|
||||
|
||||
TopBookmarkItemView view = (TopBookmarkItemView) bindView;
|
||||
view.setTitle(title);
|
||||
view.setUrl(url);
|
||||
view.setPinned(pinned);
|
||||
|
||||
// If there is no url, then show "add bookmark".
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
view.displayThumbnail(R.drawable.top_bookmark_add);
|
||||
} else {
|
||||
// Show the thumbnail.
|
||||
Thumbnail thumbnail = (mThumbnails != null ? mThumbnails.get(url) : null);
|
||||
if (thumbnail == null) {
|
||||
view.displayThumbnail(null);
|
||||
} else if (thumbnail.isThumbnail) {
|
||||
view.displayThumbnail(thumbnail.bitmap);
|
||||
} else {
|
||||
view.displayFavicon(thumbnail.bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return new TopBookmarkItemView(context);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
/* -*- 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 org.mozilla.gecko.db.BrowserDB.TopSitesCursorWrapper;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.View;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.GridLayoutAnimationController;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.GridView;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* A grid view of top bookmarks and pinned tabs.
|
||||
* Each cell in the grid is a TopBookmarkItemView.
|
||||
*/
|
||||
public class TopBookmarksView extends GridView {
|
||||
private static final String LOGTAG = "GeckoTopBookmarksView";
|
||||
|
||||
// Listener for pinning bookmarks.
|
||||
public static interface OnPinBookmarkListener {
|
||||
public void onPinBookmark(int position);
|
||||
}
|
||||
|
||||
// Max number of bookmarks that needs to be shown.
|
||||
private final int mMaxBookmarks;
|
||||
|
||||
// Number of columns to show.
|
||||
private final int mNumColumns;
|
||||
|
||||
// Horizontal spacing in between the rows.
|
||||
private final int mHorizontalSpacing;
|
||||
|
||||
// Vertical spacing in between the rows.
|
||||
private final int mVerticalSpacing;
|
||||
|
||||
// On URL open listener.
|
||||
private OnUrlOpenListener mUrlOpenListener;
|
||||
|
||||
// Pin bookmark listener.
|
||||
private OnPinBookmarkListener mPinBookmarkListener;
|
||||
|
||||
// Context menu info.
|
||||
private TopBookmarksContextMenuInfo mContextMenuInfo;
|
||||
|
||||
public TopBookmarksView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TopBookmarksView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.topBookmarksViewStyle);
|
||||
}
|
||||
|
||||
public TopBookmarksView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
mMaxBookmarks = getResources().getInteger(R.integer.number_of_top_sites);
|
||||
mNumColumns = getResources().getInteger(R.integer.number_of_top_sites_cols);
|
||||
setNumColumns(mNumColumns);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopBookmarksView, defStyle, 0);
|
||||
mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.TopBookmarksView_android_horizontalSpacing, 0x00);
|
||||
mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.TopBookmarksView_android_verticalSpacing, 0x00);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
TopBookmarkItemView row = (TopBookmarkItemView) view;
|
||||
String url = row.getUrl();
|
||||
|
||||
// If the url is empty, the user can pin a bookmark.
|
||||
// If not, navigate to the page given by the url.
|
||||
if (!TextUtils.isEmpty(url)) {
|
||||
if (mUrlOpenListener != null) {
|
||||
mUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
|
||||
}
|
||||
} else {
|
||||
if (mPinBookmarkListener != null) {
|
||||
mPinBookmarkListener.onPinBookmark(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Cursor cursor = (Cursor) parent.getItemAtPosition(position);
|
||||
mContextMenuInfo = new TopBookmarksContextMenuInfo(view, position, id, cursor);
|
||||
return showContextMenuForChild(TopBookmarksView.this);
|
||||
}
|
||||
});
|
||||
|
||||
final GridLayoutAnimationController controller = new GridLayoutAnimationController(AnimationUtils.loadAnimation(getContext(), R.anim.grow_fade_in_center));
|
||||
setLayoutAnimation(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
mUrlOpenListener = null;
|
||||
mPinBookmarkListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getColumnWidth() {
|
||||
// This method will be called from onMeasure() too.
|
||||
// It's better to use getMeasuredWidth(), as it is safe in this case.
|
||||
final int totalHorizontalSpacing = mNumColumns > 0 ? (mNumColumns - 1) * mHorizontalSpacing : 0;
|
||||
return (getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - totalHorizontalSpacing) / mNumColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
// Sets the padding for this view.
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
final int measuredWidth = getMeasuredWidth();
|
||||
final int childWidth = getColumnWidth();
|
||||
int childHeight = 0;
|
||||
|
||||
// Set the column width as the thumbnail width.
|
||||
ThumbnailHelper.getInstance().setThumbnailWidth(childWidth);
|
||||
|
||||
// If there's an adapter, use it to calculate the height of this view.
|
||||
final TopBookmarksAdapter adapter = (TopBookmarksAdapter) getAdapter();
|
||||
final int count;
|
||||
|
||||
// There shouldn't be any inherent size (due to padding) if there are no child views.
|
||||
if (adapter == null || (count = adapter.getCount()) == 0) {
|
||||
setMeasuredDimension(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the first child from the adapter.
|
||||
final View child = adapter.getView(0, null, this);
|
||||
if (child != null) {
|
||||
// Set a default LayoutParams on the child, if it doesn't have one on its own.
|
||||
AbsListView.LayoutParams params = (AbsListView.LayoutParams) child.getLayoutParams();
|
||||
if (params == null) {
|
||||
params = new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT,
|
||||
AbsListView.LayoutParams.WRAP_CONTENT);
|
||||
child.setLayoutParams(params);
|
||||
}
|
||||
|
||||
// Measure the exact width of the child, and the height based on the width.
|
||||
// Note: the child (and BookmarkThumbnailView) takes care of calculating its height.
|
||||
int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
|
||||
int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
||||
child.measure(childWidthSpec, childHeightSpec);
|
||||
childHeight = child.getMeasuredHeight();
|
||||
}
|
||||
|
||||
// Find the minimum of bookmarks we need to show, and the one given by the cursor.
|
||||
final int total = Math.min(count > 0 ? count : Integer.MAX_VALUE, mMaxBookmarks);
|
||||
|
||||
// Number of rows required to show these bookmarks.
|
||||
final int rows = (int) Math.ceil((double) total / mNumColumns);
|
||||
final int childrenHeight = childHeight * rows;
|
||||
final int totalVerticalSpacing = rows > 0 ? (rows - 1) * mVerticalSpacing : 0;
|
||||
|
||||
// Total height of this view.
|
||||
final int measuredHeight = childrenHeight + getPaddingTop() + getPaddingBottom() + totalVerticalSpacing;
|
||||
setMeasuredDimension(measuredWidth, measuredHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContextMenuInfo getContextMenuInfo() {
|
||||
return mContextMenuInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an url open listener to be used by this view.
|
||||
*
|
||||
* @param listener An url open listener for this view.
|
||||
*/
|
||||
public void setOnUrlOpenListener(OnUrlOpenListener listener) {
|
||||
mUrlOpenListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a pin bookmark listener to be used by this view.
|
||||
*
|
||||
* @param listener A pin bookmark listener for this view.
|
||||
*/
|
||||
public void setOnPinBookmarkListener(OnPinBookmarkListener listener) {
|
||||
mPinBookmarkListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* A ContextMenuInfo for TopBoomarksView that adds details from the cursor.
|
||||
*/
|
||||
public static class TopBookmarksContextMenuInfo extends AdapterContextMenuInfo {
|
||||
public String url;
|
||||
public String title;
|
||||
public boolean isPinned;
|
||||
|
||||
public TopBookmarksContextMenuInfo(View targetView, int position, long id, Cursor cursor) {
|
||||
super(targetView, position, id);
|
||||
|
||||
if (cursor == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
|
||||
title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
|
||||
isPinned = ((TopSitesCursorWrapper) cursor).isPinned();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
/* -*- 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.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.widget.FaviconView;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class TwoLinePageRow extends LinearLayout
|
||||
implements Tabs.OnTabsChangedListener {
|
||||
private static final int NO_ICON = 0;
|
||||
|
||||
private final TextView mTitle;
|
||||
private final TextView mUrl;
|
||||
private final FaviconView mFavicon;
|
||||
|
||||
private int mUrlIconId;
|
||||
private int mBookmarkIconId;
|
||||
private boolean mShowIcons;
|
||||
|
||||
// The URL for the page corresponding to this view.
|
||||
private String mPageUrl;
|
||||
|
||||
public TwoLinePageRow(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TwoLinePageRow(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
setGravity(Gravity.CENTER_VERTICAL);
|
||||
|
||||
mUrlIconId = NO_ICON;
|
||||
mBookmarkIconId = NO_ICON;
|
||||
mShowIcons = true;
|
||||
|
||||
LayoutInflater.from(context).inflate(R.layout.two_line_page_row, this);
|
||||
mTitle = (TextView) findViewById(R.id.title);
|
||||
mUrl = (TextView) findViewById(R.id.url);
|
||||
mFavicon = (FaviconView) findViewById(R.id.favicon);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
Tabs.registerOnTabsChangedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
// Delay removing the listener to avoid modifying mTabsChangedListeners
|
||||
// while notifyListeners is iterating through the array.
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Tabs.unregisterOnTabsChangedListener(TwoLinePageRow.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final Object data) {
|
||||
switch(msg) {
|
||||
case ADDED:
|
||||
case CLOSED:
|
||||
case LOCATION_CHANGE:
|
||||
updateDisplayedUrl();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setTitle(String title) {
|
||||
mTitle.setText(title);
|
||||
}
|
||||
|
||||
private void setUrl(String url) {
|
||||
mUrl.setText(url);
|
||||
}
|
||||
|
||||
private void setUrl(int stringId) {
|
||||
mUrl.setText(stringId);
|
||||
}
|
||||
|
||||
private void setUrlIcon(int urlIconId) {
|
||||
if (mUrlIconId == urlIconId) {
|
||||
return;
|
||||
}
|
||||
|
||||
mUrlIconId = urlIconId;
|
||||
mUrl.setCompoundDrawablesWithIntrinsicBounds(mUrlIconId, 0, mBookmarkIconId, 0);
|
||||
}
|
||||
|
||||
private void setFaviconWithUrl(Bitmap favicon, String url) {
|
||||
mFavicon.updateImage(favicon, url);
|
||||
}
|
||||
|
||||
private void setBookmarkIcon(int bookmarkIconId) {
|
||||
if (mBookmarkIconId == bookmarkIconId) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBookmarkIconId = bookmarkIconId;
|
||||
mUrl.setCompoundDrawablesWithIntrinsicBounds(mUrlIconId, 0, mBookmarkIconId, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the page URL, so that we can use it to replace "Switch to tab" if the open
|
||||
* tab changes or is closed.
|
||||
*/
|
||||
private void updateDisplayedUrl(String url) {
|
||||
mPageUrl = url;
|
||||
updateDisplayedUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the page URL with "Switch to tab" if there is already a tab open with that URL.
|
||||
*/
|
||||
private void updateDisplayedUrl() {
|
||||
int tabId = Tabs.getInstance().getTabIdForUrl(mPageUrl);
|
||||
if (!mShowIcons || tabId < 0) {
|
||||
setUrl(mPageUrl);
|
||||
setUrlIcon(NO_ICON);
|
||||
} else {
|
||||
setUrl(R.string.switch_to_tab);
|
||||
setUrlIcon(R.drawable.ic_url_bar_tab);
|
||||
}
|
||||
}
|
||||
|
||||
public void setShowIcons(boolean showIcons) {
|
||||
mShowIcons = showIcons;
|
||||
}
|
||||
|
||||
public void updateFromCursor(Cursor cursor) {
|
||||
if (cursor == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int titleIndex = cursor.getColumnIndexOrThrow(URLColumns.TITLE);
|
||||
final String title = cursor.getString(titleIndex);
|
||||
|
||||
int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL);
|
||||
final String url = cursor.getString(urlIndex);
|
||||
|
||||
// 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
|
||||
setTitle(TextUtils.isEmpty(title) ? url : title);
|
||||
|
||||
updateDisplayedUrl(url);
|
||||
|
||||
int faviconIndex = cursor.getColumnIndex(URLColumns.FAVICON);
|
||||
if (faviconIndex != -1) {
|
||||
byte[] b = cursor.getBlob(faviconIndex);
|
||||
|
||||
Bitmap favicon = null;
|
||||
if (b != null) {
|
||||
Bitmap bitmap = BitmapUtils.decodeByteArray(b);
|
||||
if (bitmap != null) {
|
||||
favicon = Favicons.getInstance().scaleImage(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
setFaviconWithUrl(favicon, url);
|
||||
} else {
|
||||
// If favicons is not on the cursor, try to fetch it from the memory cache
|
||||
setFaviconWithUrl(Favicons.getInstance().getFaviconFromMemCache(url), url);
|
||||
}
|
||||
|
||||
// Don't show bookmark/reading list icon, if not needed.
|
||||
if (!mShowIcons) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int bookmarkIdIndex = cursor.getColumnIndex(Combined.BOOKMARK_ID);
|
||||
if (bookmarkIdIndex != -1) {
|
||||
final long bookmarkId = cursor.getLong(bookmarkIdIndex);
|
||||
final int displayIndex = cursor.getColumnIndex(Combined.DISPLAY);
|
||||
|
||||
final int display;
|
||||
if (displayIndex != -1) {
|
||||
display = cursor.getInt(displayIndex);
|
||||
} else {
|
||||
display = Combined.DISPLAY_NORMAL;
|
||||
}
|
||||
|
||||
// The bookmark id will be 0 (null in database) when the url
|
||||
// is not a bookmark.
|
||||
if (bookmarkId == 0) {
|
||||
setBookmarkIcon(NO_ICON);
|
||||
} else if (display == Combined.DISPLAY_READER) {
|
||||
setBookmarkIcon(R.drawable.ic_url_bar_reader);
|
||||
} else {
|
||||
setBookmarkIcon(R.drawable.ic_url_bar_star);
|
||||
}
|
||||
} else {
|
||||
setBookmarkIcon(NO_ICON);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,10 +6,13 @@
|
|||
<!ENTITY no_space_to_start_error "There is not enough space available for &brandShortName; to start.">
|
||||
<!ENTITY error_loading_file "An error occurred when trying to load files required to run &brandShortName;">
|
||||
|
||||
<!ENTITY awesomebar_all_pages_title "Top Sites">
|
||||
<!ENTITY awesomebar_bookmarks_title "Bookmarks">
|
||||
<!ENTITY awesomebar_history_title "History">
|
||||
<!ENTITY awesomebar_switch_to_tab "Switch to tab">
|
||||
<!-- Localization note: These are used as the titles of different pages on the home screen.
|
||||
They are automatically converted to all caps by the Android platform. -->
|
||||
<!ENTITY bookmarks_title "Bookmarks">
|
||||
<!ENTITY history_title "History">
|
||||
<!ENTITY reading_list_title "Reading List">
|
||||
|
||||
<!ENTITY switch_to_tab "Switch to tab">
|
||||
|
||||
<!ENTITY crash_reporter_title "&brandShortName; Crash Reporter">
|
||||
<!ENTITY crash_message2 "&brandShortName; had a problem and crashed. Your tabs should be listed on the &brandShortName; Start page when you restart.">
|
||||
|
@ -30,10 +33,7 @@
|
|||
|
||||
<!ENTITY choose_file "Choose File">
|
||||
|
||||
<!ENTITY awesomebar_default_text "Enter Search or Address">
|
||||
|
||||
<!ENTITY remote_tabs "Synced Tabs">
|
||||
<!ENTITY remote_tabs_show_all "Show all tabs">
|
||||
<!ENTITY url_bar_default_text "Enter Search or Address">
|
||||
|
||||
<!ENTITY bookmark "Bookmark">
|
||||
<!ENTITY bookmark_added "Bookmark added">
|
||||
|
@ -217,8 +217,7 @@ size. -->
|
|||
<!ENTITY contextmenu_open_new_tab "Open in New Tab">
|
||||
<!ENTITY contextmenu_open_private_tab "Open in Private Tab">
|
||||
<!ENTITY contextmenu_open_in_reader "Open in Reader">
|
||||
<!ENTITY contextmenu_remove_history "Remove">
|
||||
<!ENTITY contextmenu_remove_bookmark "Remove">
|
||||
<!ENTITY contextmenu_remove "Remove">
|
||||
<!ENTITY contextmenu_add_to_launcher "Add to Home Screen">
|
||||
<!ENTITY contextmenu_share "Share">
|
||||
<!ENTITY contextmenu_pasteandgo "Paste & Go">
|
||||
|
@ -227,6 +226,9 @@ size. -->
|
|||
<!ENTITY contextmenu_edit_bookmark "Edit">
|
||||
<!ENTITY contextmenu_subscribe "Subscribe to Page">
|
||||
<!ENTITY contextmenu_site_settings "Edit Site Settings">
|
||||
<!ENTITY contextmenu_top_bookmarks_edit "Edit">
|
||||
<!ENTITY contextmenu_top_bookmarks_pin "Pin Site">
|
||||
<!ENTITY contextmenu_top_bookmarks_unpin "Unpin Site">
|
||||
|
||||
<!ENTITY pref_titlebar_mode "Title bar">
|
||||
<!ENTITY pref_titlebar_mode_title "Show page title">
|
||||
|
@ -234,6 +236,7 @@ size. -->
|
|||
|
||||
<!ENTITY history_removed "Page removed">
|
||||
|
||||
<!ENTITY bookmark_add "Add a bookmark">
|
||||
<!ENTITY bookmark_edit_title "Edit Bookmark">
|
||||
<!ENTITY bookmark_edit_name "Name">
|
||||
<!ENTITY bookmark_edit_location "Location">
|
||||
|
@ -246,7 +249,6 @@ size. -->
|
|||
<!ENTITY site_settings_clear "Clear">
|
||||
<!ENTITY site_settings_no_settings "There are no settings to clear.">
|
||||
|
||||
<!ENTITY reading_list "Reading List">
|
||||
<!ENTITY reading_list_added "Page added to your Reading List">
|
||||
<!ENTITY reading_list_removed "Page removed from your Reading List">
|
||||
<!ENTITY reading_list_failed "Failed to add page to your Reading List">
|
||||
|
@ -269,28 +271,22 @@ size. -->
|
|||
<!ENTITY button_set "Set">
|
||||
<!ENTITY button_clear "Clear">
|
||||
|
||||
<!ENTITY abouthome_addons_title "Add-ons for your &brandShortName;">
|
||||
<!ENTITY abouthome_addons_browse "Browse all &brandShortName; add-ons">
|
||||
<!ENTITY abouthome_last_tabs_title "Your tabs from last time">
|
||||
<!ENTITY abouthome_last_tabs_open "Open all tabs from last time">
|
||||
<!ENTITY abouthome_top_sites_title "Top sites">
|
||||
<!ENTITY abouthome_topsites_edit "Edit">
|
||||
<!ENTITY abouthome_topsites_pin "Pin Site">
|
||||
<!ENTITY abouthome_topsites_unpin "Unpin Site">
|
||||
<!ENTITY home_history_title "History">
|
||||
<!ENTITY home_last_tabs_title "Tabs from last time">
|
||||
<!ENTITY home_last_tabs_open "Open all tabs from last time">
|
||||
<!ENTITY home_last_tabs_empty "Your recent tabs show up here.">
|
||||
<!ENTITY home_most_recent_title "Most recent">
|
||||
<!ENTITY home_most_recent_empty "Websites you visited most recently show up here.">
|
||||
<!ENTITY home_most_visited_title "Most visited">
|
||||
<!ENTITY home_reading_list_empty "Articles you save for later show up here.">
|
||||
<!-- Localization note (home_reading_list_hint): The "TIP" string is synonymous to "hint", "clue", etc. This string is displayed
|
||||
as an advisory message on how to add content to the reading list when the reading list empty.
|
||||
The "%I" in the string will be replaced by a small image of the icon described, and can be moved to wherever
|
||||
it is applicable.. -->
|
||||
<!ENTITY home_reading_list_hint "TIP: Save articles to your reading list by long pressing the %I icon when it appears in the title bar.">
|
||||
|
||||
<!-- Localization note (abouthome_about_sync3, abouthome_about_apps2): The
|
||||
chevron (ex: "»"; unicode= U+00BB) is used as an arrow to show that
|
||||
clicking this text in the promotions box will perform some action. Note
|
||||
that a non-breaking space (unicode= U+00A0) should be used between this
|
||||
character and the remainder of the string to prevent word wrap. -->
|
||||
<!ENTITY abouthome_about_sync3 "Set up Firefox Sync to access bookmarks, history and tabs from your other devices »">
|
||||
<!ENTITY abouthome_about_apps3 "Get apps from the Firefox Marketplace and discover the best the Web has to offer »">
|
||||
<!-- Localization note (abouthome_sync_bold_name, abouthome_apps_bold_name):
|
||||
These strings are accentuated as bold text in the "abouthome_about_..."
|
||||
strings above. These strings should be a subset of the strings above and
|
||||
generally be the name of the product the string describes. -->
|
||||
<!ENTITY abouthome_sync_bold_name "Firefox Sync">
|
||||
<!ENTITY abouthome_apps_bold_name2 "Firefox Marketplace">
|
||||
<!ENTITY home_most_visited_empty "Websites you visit most frequently show up here.">
|
||||
<!ENTITY pin_bookmark_dialog_hint "Enter a search keyword">
|
||||
|
||||
<!ENTITY filepicker_title "Choose File">
|
||||
<!ENTITY filepicker_audio_title "Choose or record a sound">
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<alpha android:interpolator="@android:anim/accelerate_interpolator"
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0"
|
||||
android:duration="100"/>
|
||||
|
||||
</set>
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<alpha android:interpolator="@android:anim/accelerate_interpolator"
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.0"
|
||||
android:duration="200"/>
|
||||
|
||||
</set>
|
|
@ -1,18 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<!-- dark theme -->
|
||||
<item gecko:state_dark="true" android:color="#CCFFFFFF" />
|
||||
|
||||
<!-- light theme -->
|
||||
<item gecko:state_light="true" android:color="#CC222222" />
|
||||
|
||||
<!-- default -->
|
||||
<item android:color="@color/text_color_secondary"/>
|
||||
|
||||
</selector>
|
|
@ -1,18 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<!-- dark theme -->
|
||||
<item gecko:state_dark="true" android:color="@color/text_color_primary_inverse" />
|
||||
|
||||
<!-- light theme -->
|
||||
<item gecko:state_light="true" android:color="@color/text_color_primary" />
|
||||
|
||||
<!-- default -->
|
||||
<item android:color="@color/text_color_primary"/>
|
||||
|
||||
</selector>
|
|
@ -4,9 +4,11 @@
|
|||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true"
|
||||
android:drawable="@color/awesomebar_header_row_focused"/>
|
||||
|
||||
<item android:drawable="@color/awesomebar_header_row"/>
|
||||
<!-- empty with no url -->
|
||||
<item android:state_empty="true" android:color="#80777777" />
|
||||
|
||||
<!-- default -->
|
||||
<item android:color="#FF777777"/>
|
||||
|
||||
</selector>
|
Двоичные данные
mobile/android/base/resources/drawable-hdpi/abouthome_icon.png
До Ширина: | Высота: | Размер: 55 KiB |
До Ширина: | Высота: | Размер: 1.4 KiB |
До Ширина: | Высота: | Размер: 2.2 KiB |
До Ширина: | Высота: | Размер: 957 B |
До Ширина: | Высота: | Размер: 932 B |
До Ширина: | Высота: | Размер: 11 KiB |
До Ширина: | Высота: | Размер: 12 KiB |
До Ширина: | Высота: | Размер: 25 KiB После Ширина: | Высота: | Размер: 3.3 KiB |
До Ширина: | Высота: | Размер: 249 B |
До Ширина: | Высота: | Размер: 1.2 KiB |
После Ширина: | Высота: | Размер: 264 B |
После Ширина: | Высота: | Размер: 506 B |
Двоичные данные
mobile/android/base/resources/drawable-hdpi/history_tabs_indicator_selected.9.png
Normal file
После Ширина: | Высота: | Размер: 2.7 KiB |
После Ширина: | Высота: | Размер: 89 B |
Двоичные данные
mobile/android/base/resources/drawable-hdpi/ic_addons_empty.png
До Ширина: | Высота: | Размер: 3.1 KiB |
До Ширина: | Высота: | Размер: 372 B |
До Ширина: | Высота: | Размер: 398 B |
До Ширина: | Высота: | Размер: 297 B |
До Ширина: | Высота: | Размер: 291 B После Ширина: | Высота: | Размер: 291 B |
После Ширина: | Высота: | Размер: 317 B |
До Ширина: | Высота: | Размер: 497 B После Ширина: | Высота: | Размер: 497 B |
После Ширина: | Высота: | Размер: 306 B |
После Ширина: | Высота: | Размер: 221 B |
После Ширина: | Высота: | Размер: 446 B |
После Ширина: | Высота: | Размер: 1.3 KiB |
После Ширина: | Высота: | Размер: 841 B |
После Ширина: | Высота: | Размер: 2.3 KiB |
После Ширина: | Высота: | Размер: 421 B |
После Ширина: | Высота: | Размер: 1.3 KiB |