blob: c7fb9c65d0320ac7db27ae69b087438361532486 [file] [log] [blame]
/*
* Copyright (C) 2010 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 com.android.browser;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ObjectAnimator;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.speech.RecognizerResultsIntent;
import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.webkit.WebView;
import android.widget.AbsoluteLayout;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;
import com.android.browser.UI.DropdownChangeListener;
import com.android.browser.UrlInputView.UrlInputListener;
import com.android.browser.autocomplete.SuggestedTextController.TextChangeWatcher;
import java.util.List;
/**
* Base class for a title bar used by the browser.
*/
public class TitleBarBase extends RelativeLayout
implements OnClickListener, OnFocusChangeListener, UrlInputListener,
TextChangeWatcher, DeviceAccountLogin.AutoLoginCallback,
OnMenuItemClickListener {
protected static final int PROGRESS_MAX = 100;
private static final float ANIM_TITLEBAR_DECELERATE = 2.5f;
// These need to be set by the subclass.
protected ImageView mFavicon;
protected ImageView mLockIcon;
protected UiController mUiController;
protected BaseUi mBaseUi;
protected FrameLayout mParent;
protected PageProgressView mProgress;
protected UrlInputView mUrlInput;
protected boolean mInVoiceMode;
protected View mContainer;
private View mUaSwitcher;
// Auto-login UI
protected View mAutoLogin;
protected Spinner mAutoLoginAccount;
protected Button mAutoLoginLogin;
protected ProgressBar mAutoLoginProgress;
protected TextView mAutoLoginError;
protected View mAutoLoginCancel;
protected DeviceAccountLogin mAutoLoginHandler;
protected ArrayAdapter<String> mAccountsAdapter;
protected boolean mUseQuickControls;
//state
protected boolean mShowing;
protected boolean mInLoad;
protected boolean mSkipTitleBarAnimations;
private Animator mTitleBarAnimator;
public TitleBarBase(Context context, UiController controller, BaseUi ui,
FrameLayout parent) {
super(context, null);
mUiController = controller;
mBaseUi = ui;
mParent = parent;
}
protected void initLayout(Context context, int layoutId) {
LayoutInflater factory = LayoutInflater.from(context);
factory.inflate(layoutId, this);
mContainer = findViewById(R.id.taburlbar);
mProgress = (PageProgressView) findViewById(R.id.progress);
mUrlInput = (UrlInputView) findViewById(R.id.url);
mLockIcon = (ImageView) findViewById(R.id.lock);
mUrlInput.setUrlInputListener(this);
mUrlInput.setController(mUiController);
mUrlInput.setOnFocusChangeListener(this);
mUrlInput.setSelectAllOnFocus(true);
mUrlInput.addQueryTextWatcher(this);
mAutoLogin = findViewById(R.id.autologin);
mAutoLoginAccount = (Spinner) findViewById(R.id.autologin_account);
mAutoLoginLogin = (Button) findViewById(R.id.autologin_login);
mAutoLoginLogin.setOnClickListener(this);
mAutoLoginProgress = (ProgressBar) findViewById(R.id.autologin_progress);
mAutoLoginError = (TextView) findViewById(R.id.autologin_error);
mAutoLoginCancel = mAutoLogin.findViewById(R.id.autologin_close);
mAutoLoginCancel.setOnClickListener(this);
}
protected void setupUrlInput() {
}
protected void setUseQuickControls(boolean use) {
mUseQuickControls = use;
setLayoutParams(makeLayoutParams());
}
void setShowProgressOnly(boolean progress) {
if (progress && !inAutoLogin()) {
mContainer.setVisibility(View.GONE);
} else {
mContainer.setVisibility(View.VISIBLE);
}
}
void setSkipTitleBarAnimations(boolean skip) {
mSkipTitleBarAnimations = skip;
}
void setupTitleBarAnimator(Animator animator) {
Resources res = mContext.getResources();
int duration = res.getInteger(R.integer.titlebar_animation_duration);
animator.setInterpolator(new DecelerateInterpolator(
ANIM_TITLEBAR_DECELERATE));
animator.setDuration(duration);
}
void show() {
if (mUseQuickControls) {
mParent.addView(this);
} else {
if (!mSkipTitleBarAnimations) {
cancelTitleBarAnimation(false);
int visibleHeight = getVisibleTitleHeight();
float startPos = (-getEmbeddedHeight() + visibleHeight);
if (getTranslationY() != 0) {
startPos = Math.max(startPos, getTranslationY());
}
mTitleBarAnimator = ObjectAnimator.ofFloat(this,
"translationY",
startPos, 0);
setupTitleBarAnimator(mTitleBarAnimator);
mTitleBarAnimator.start();
}
mBaseUi.setTitleGravity(Gravity.TOP);
}
mShowing = true;
}
void hide() {
if (mUseQuickControls) {
mParent.removeView(this);
} else {
if (!mSkipTitleBarAnimations) {
cancelTitleBarAnimation(false);
int visibleHeight = getVisibleTitleHeight();
mTitleBarAnimator = ObjectAnimator.ofFloat(this,
"translationY", getTranslationY(),
(-getEmbeddedHeight() + visibleHeight));
mTitleBarAnimator.addListener(mHideTileBarAnimatorListener);
setupTitleBarAnimator(mTitleBarAnimator);
mTitleBarAnimator.start();
} else {
mBaseUi.setTitleGravity(Gravity.NO_GRAVITY);
}
}
mShowing = false;
}
boolean isShowing() {
return mShowing;
}
void cancelTitleBarAnimation(boolean reset) {
if (mTitleBarAnimator != null) {
mTitleBarAnimator.cancel();
mTitleBarAnimator = null;
}
if (reset) {
setTranslationY(0);
}
}
private AnimatorListener mHideTileBarAnimatorListener = new AnimatorListener() {
boolean mWasCanceled;
@Override
public void onAnimationStart(Animator animation) {
mWasCanceled = false;
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
if (!mWasCanceled) {
setTranslationY(0);
}
mBaseUi.setTitleGravity(Gravity.NO_GRAVITY);
}
@Override
public void onAnimationCancel(Animator animation) {
mWasCanceled = true;
}
};
private int getVisibleTitleHeight() {
Tab tab = mBaseUi.getActiveTab();
WebView webview = tab != null ? tab.getWebView() : null;
return webview != null ? webview.getVisibleTitleHeight() : 0;
}
/**
* Update the progress, from 0 to 100.
*/
void setProgress(int newProgress) {
if (newProgress >= PROGRESS_MAX) {
mProgress.setProgress(PageProgressView.MAX_PROGRESS);
mProgress.setVisibility(View.GONE);
mInLoad = false;
onProgressStopped();
// check if needs to be hidden
if (!isEditingUrl() && !inAutoLogin()) {
hide();
if (mUseQuickControls) {
setShowProgressOnly(false);
}
}
} else {
if (!mInLoad) {
mProgress.setVisibility(View.VISIBLE);
mInLoad = true;
onProgressStarted();
}
mProgress.setProgress(newProgress * PageProgressView.MAX_PROGRESS
/ PROGRESS_MAX);
if (!mShowing) {
if (mUseQuickControls && !isEditingUrl()) {
setShowProgressOnly(true);
}
show();
}
}
}
protected void onProgressStarted() {
}
protected void onProgressStopped() {
}
/* package */ void setLock(Drawable d) {
assert mLockIcon != null;
if (null == d) {
mLockIcon.setVisibility(View.GONE);
} else {
mLockIcon.setImageDrawable(d);
mLockIcon.setVisibility(View.VISIBLE);
}
}
/* package */ void setFavicon(Bitmap icon) {
mFavicon.setImageDrawable(mBaseUi.getFaviconDrawable(icon));
}
public int getEmbeddedHeight() {
int height = mContainer.getHeight();
if (mAutoLogin.getVisibility() == View.VISIBLE) {
height += mAutoLogin.getHeight();
}
return height;
}
protected void updateAutoLogin(Tab tab, boolean animate) {
DeviceAccountLogin login = tab.getDeviceAccountLogin();
if (login != null) {
mAutoLoginHandler = login;
ContextThemeWrapper wrapper = new ContextThemeWrapper(mContext,
android.R.style.Theme_Holo_Light);
mAccountsAdapter = new ArrayAdapter<String>(wrapper,
android.R.layout.simple_spinner_item, login.getAccountNames());
mAccountsAdapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
mAutoLoginAccount.setAdapter(mAccountsAdapter);
mAutoLoginAccount.setSelection(0);
mAutoLoginAccount.setEnabled(true);
mAutoLoginLogin.setEnabled(true);
mAutoLoginProgress.setVisibility(View.INVISIBLE);
mAutoLoginError.setVisibility(View.GONE);
switch (login.getState()) {
case DeviceAccountLogin.PROCESSING:
mAutoLoginAccount.setEnabled(false);
mAutoLoginLogin.setEnabled(false);
mAutoLoginProgress.setVisibility(View.VISIBLE);
break;
case DeviceAccountLogin.FAILED:
mAutoLoginProgress.setVisibility(View.INVISIBLE);
mAutoLoginError.setVisibility(View.VISIBLE);
break;
case DeviceAccountLogin.INITIAL:
break;
default:
throw new IllegalStateException();
}
showAutoLogin(animate);
} else {
hideAutoLogin(animate);
}
}
protected void showAutoLogin(boolean animate) {
if (mUseQuickControls) {
mBaseUi.showTitleBar();
}
mAutoLogin.setVisibility(View.VISIBLE);
if (animate) {
mAutoLogin.startAnimation(AnimationUtils.loadAnimation(
getContext(), R.anim.autologin_enter));
}
}
protected void hideAutoLogin(boolean animate) {
mAutoLoginHandler = null;
if (mUseQuickControls) {
mBaseUi.hideTitleBar();
mAutoLogin.setVisibility(View.GONE);
mBaseUi.refreshWebView();
} else {
if (animate) {
Animation anim = AnimationUtils.loadAnimation(getContext(),
R.anim.autologin_exit);
anim.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationEnd(Animation a) {
mAutoLogin.setVisibility(View.GONE);
mBaseUi.refreshWebView();
}
@Override
public void onAnimationStart(Animation a) {
}
@Override
public void onAnimationRepeat(Animation a) {
}
});
mAutoLogin.startAnimation(anim);
} else if (mAutoLogin.getAnimation() == null) {
mAutoLogin.setVisibility(View.GONE);
mBaseUi.refreshWebView();
}
}
}
@Override
public void loginFailed() {
mAutoLoginAccount.setEnabled(true);
mAutoLoginLogin.setEnabled(true);
mAutoLoginProgress.setVisibility(View.INVISIBLE);
mAutoLoginError.setVisibility(View.VISIBLE);
}
protected boolean inAutoLogin() {
return mAutoLoginHandler != null;
}
public void setUaSwitcher(View v) {
if (mUaSwitcher != null) {
mUaSwitcher.setOnClickListener(null);
}
mUaSwitcher = v;
mUaSwitcher.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (mAutoLoginCancel == v) {
if (mAutoLoginHandler != null) {
mAutoLoginHandler.cancel();
mAutoLoginHandler = null;
}
hideAutoLogin(true);
} else if (mAutoLoginLogin == v) {
if (mAutoLoginHandler != null) {
mAutoLoginAccount.setEnabled(false);
mAutoLoginLogin.setEnabled(false);
mAutoLoginProgress.setVisibility(View.VISIBLE);
mAutoLoginError.setVisibility(View.GONE);
mAutoLoginHandler.login(
mAutoLoginAccount.getSelectedItemPosition(), this);
}
} else if (mUaSwitcher == v) {
BrowserSettings settings = BrowserSettings.getInstance();
WebView web = getCurrentWebView();
if (web == null) return;
boolean desktop = settings.hasDesktopUseragent(web);
PopupMenu popup = new PopupMenu(mContext, mUaSwitcher);
Menu menu = popup.getMenu();
popup.getMenuInflater().inflate(R.menu.ua_switcher, menu);
menu.findItem(R.id.ua_mobile_menu_id).setChecked(!desktop);
menu.findItem(R.id.ua_desktop_menu_id).setChecked(desktop);
popup.setOnMenuItemClickListener(this);
popup.show();
}
}
@Override
public boolean onMenuItemClick(MenuItem item) {
BrowserSettings settings = BrowserSettings.getInstance();
WebView web = getCurrentWebView();
if (web == null) return false;
boolean desktop = settings.hasDesktopUseragent(web);
switch (item.getItemId()) {
case R.id.ua_mobile_menu_id:
if (desktop) {
settings.toggleDesktopUseragent(web);
web.loadUrl(web.getOriginalUrl());
}
return true;
case R.id.ua_desktop_menu_id:
if (!desktop) {
settings.toggleDesktopUseragent(web);
web.loadUrl(web.getOriginalUrl());
}
return true;
}
return false;
}
@Override
public void onFocusChange(View view, boolean hasFocus) {
// if losing focus and not in touch mode, leave as is
if (hasFocus || view.isInTouchMode() || mUrlInput.needsUpdate()) {
setFocusState(hasFocus);
}
if (hasFocus) {
mBaseUi.showTitleBar();
mUrlInput.forceIme();
if (mInVoiceMode) {
mUrlInput.forceFilter();
}
} else if (!mUrlInput.needsUpdate()) {
mUrlInput.dismissDropDown();
mUrlInput.hideIME();
if (mUrlInput.getText().length() == 0) {
Tab currentTab = mUiController.getTabControl().getCurrentTab();
if (currentTab != null) {
mUrlInput.setText(currentTab.getUrl(), false);
}
}
mBaseUi.suggestHideTitleBar();
}
mUrlInput.clearNeedsUpdate();
}
protected void setFocusState(boolean focus) {
if (focus) {
updateSearchMode(false);
}
}
protected void updateSearchMode(boolean userEdited) {
setSearchMode(!userEdited || TextUtils.isEmpty(mUrlInput.getUserText()));
}
protected void setSearchMode(boolean voiceSearchEnabled) {}
boolean isEditingUrl() {
return mUrlInput.hasFocus();
}
void stopEditingUrl() {
mUrlInput.clearFocus();
}
void setDisplayTitle(String title) {
if (!isEditingUrl()) {
mUrlInput.setText(title, false);
}
}
// UrlInput text watcher
@Override
public void onTextChanged(String newText) {
if (mUrlInput.hasFocus()) {
// check if input field is empty and adjust voice search state
updateSearchMode(true);
// clear voice mode when user types
setInVoiceMode(false, null);
}
}
// voicesearch
public void setInVoiceMode(boolean voicemode, List<String> voiceResults) {
mInVoiceMode = voicemode;
mUrlInput.setVoiceResults(voiceResults);
}
void setIncognitoMode(boolean incognito) {
mUrlInput.setIncognitoMode(incognito);
}
void clearCompletions() {
mUrlInput.setSuggestedText(null);
}
// UrlInputListener implementation
/**
* callback from suggestion dropdown
* user selected a suggestion
*/
@Override
public void onAction(String text, String extra, String source) {
mUiController.getCurrentTopWebView().requestFocus();
mBaseUi.hideTitleBar();
Intent i = new Intent();
String action = null;
if (UrlInputView.VOICE.equals(source)) {
action = RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS;
source = null;
} else {
action = Intent.ACTION_SEARCH;
}
i.setAction(action);
i.putExtra(SearchManager.QUERY, text);
if (extra != null) {
i.putExtra(SearchManager.EXTRA_DATA_KEY, extra);
}
if (source != null) {
Bundle appData = new Bundle();
appData.putString(com.android.common.Search.SOURCE, source);
i.putExtra(SearchManager.APP_DATA, appData);
}
mUiController.handleNewIntent(i);
setDisplayTitle(text);
}
@Override
public void onDismiss() {
final Tab currentTab = mBaseUi.getActiveTab();
mBaseUi.hideTitleBar();
post(new Runnable() {
public void run() {
clearFocus();
if ((currentTab != null) && !mInVoiceMode) {
setDisplayTitle(currentTab.getUrl());
}
}
});
}
/**
* callback from the suggestion dropdown
* copy text to input field and stay in edit mode
*/
@Override
public void onCopySuggestion(String text) {
mUrlInput.setText(text, true);
if (text != null) {
mUrlInput.setSelection(text.length());
}
}
public void setCurrentUrlIsBookmark(boolean isBookmark) {
}
@Override
public boolean dispatchKeyEventPreIme(KeyEvent evt) {
if (evt.getKeyCode() == KeyEvent.KEYCODE_BACK) {
// catch back key in order to do slightly more cleanup than usual
mUrlInput.clearFocus();
return true;
}
return super.dispatchKeyEventPreIme(evt);
}
protected WebView getCurrentWebView() {
Tab t = mBaseUi.getActiveTab();
if (t != null) {
return t.getWebView();
} else {
return null;
}
}
void registerDropdownChangeListener(DropdownChangeListener d) {
mUrlInput.registerDropdownChangeListener(d);
}
/**
* called from the Ui when the user wants to edit
* @param clearInput clear the input field
*/
void startEditingUrl(boolean clearInput) {
// editing takes preference of progress
mContainer.setVisibility(View.VISIBLE);
if (mUseQuickControls) {
mProgress.setVisibility(View.GONE);
}
if (!mUrlInput.hasFocus()) {
mUrlInput.requestFocus();
}
if (clearInput) {
mUrlInput.setText("");
} else if (mInVoiceMode) {
mUrlInput.showDropDown();
}
}
private ViewGroup.LayoutParams makeLayoutParams() {
if (mUseQuickControls) {
return new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT);
} else {
return new AbsoluteLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT,
0, 0);
}
}
}