summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMichael Kolb <kolby@google.com>2010-12-15 11:52:57 -0800
committerMichael Kolb <kolby@google.com>2010-12-15 11:53:02 -0800
commit376b54116e38b3b94c4d64663d1bff38352b0e59 (patch)
tree97d9f406203b494b7dc529d17d8728858942d3ad /src
parent439c9a58765aa6aab95d55422ee61ea8360e912d (diff)
downloadpackages_apps_browser-376b54116e38b3b94c4d64663d1bff38352b0e59.zip
packages_apps_browser-376b54116e38b3b94c4d64663d1bff38352b0e59.tar.gz
packages_apps_browser-376b54116e38b3b94c4d64663d1bff38352b0e59.tar.bz2
Add quick controls
Bug: http://b/issue?id=3277888 Added Quick Controls Lab setting Implemented Quick Controls UI Change-Id: I72011daf9140aa5d15c8b785126867c10bbc5501
Diffstat (limited to 'src')
-rw-r--r--src/com/android/browser/BaseUi.java16
-rw-r--r--src/com/android/browser/BrowserSettings.java13
-rw-r--r--src/com/android/browser/PhoneUi.java22
-rw-r--r--src/com/android/browser/PieControl.java151
-rw-r--r--src/com/android/browser/TabBar.java15
-rw-r--r--src/com/android/browser/TitleBarXLarge.java30
-rw-r--r--src/com/android/browser/XLargeUi.java131
-rw-r--r--src/com/android/browser/preferences/LabPreferencesFragment.java58
-rw-r--r--src/com/android/browser/view/PieMenu.java463
9 files changed, 874 insertions, 25 deletions
diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java
index 95e795c..1e9038d 100644
--- a/src/com/android/browser/BaseUi.java
+++ b/src/com/android/browser/BaseUi.java
@@ -231,22 +231,6 @@ public abstract class BaseUi implements UI, WebViewFactory {
mActiveTab = tab;
attachTabToContentView(tab);
setShouldShowErrorConsole(tab, mUiController.shouldShowErrorConsole());
- WebView view = tab.getWebView();
- // TabControl.setCurrentTab has been called before this,
- // so the tab is guaranteed to have a webview
- if (view == null) {
- Log.e(LOGTAG, "active tab with no webview detected");
- return;
- }
- view.setEmbeddedTitleBar(getEmbeddedTitleBar());
- if (tab.isInVoiceSearchMode()) {
- showVoiceTitleBar(tab.getVoiceDisplayTitle());
- } else {
- revertVoiceTitleBar(tab);
- }
- resetTitleIconAndProgress(tab);
- updateLockIconToLatest(tab);
- tab.getTopWindow().requestFocus();
}
Tab getActiveTab() {
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 1f091e2..1b8acc6 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -117,6 +117,9 @@ public class BrowserSettings extends Observable implements OnSharedPreferenceCha
private boolean navDump = false;
private boolean hardwareAccelerated = true;
+ // Lab settings
+ private boolean quickControls = false;
+
// By default the error console is shown once the user navigates to about:debug.
// The setting can be then toggled from the settings menu.
private boolean showConsole = true;
@@ -167,6 +170,8 @@ public class BrowserSettings extends Observable implements OnSharedPreferenceCha
public final static String PREF_HARDWARE_ACCEL = "enable_hardware_accel";
public final static String PREF_USER_AGENT = "user_agent";
+ public final static String PREF_QUICK_CONTROLS = "enable_quick_controls";
+
private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (Macintosh; " +
"U; Intel Mac OS X 10_6_3; en-us) AppleWebKit/533.16 (KHTML, " +
"like Gecko) Version/5.0 Safari/533.16";
@@ -490,6 +495,8 @@ public class BrowserSettings extends Observable implements OnSharedPreferenceCha
navDump = p.getBoolean("enable_nav_dump", navDump);
}
+ quickControls = p.getBoolean(PREF_QUICK_CONTROLS, quickControls);
+
// Only set these on startup if it is a dev build
if (DEV_BUILD) {
userAgent = Integer.parseInt(p.getString(PREF_USER_AGENT, "0"));
@@ -573,6 +580,10 @@ public class BrowserSettings extends Observable implements OnSharedPreferenceCha
return hardwareAccelerated;
}
+ public boolean useQuickControls() {
+ return quickControls;
+ }
+
public boolean showDebugSettings() {
return showDebugSettings;
}
@@ -836,6 +847,8 @@ public class BrowserSettings extends Observable implements OnSharedPreferenceCha
} else if (PREF_USER_AGENT.equals(key)) {
userAgent = Integer.parseInt(p.getString(PREF_USER_AGENT, "0"));
update();
+ } else if (PREF_QUICK_CONTROLS.equals(key)) {
+ quickControls = p.getBoolean(PREF_QUICK_CONTROLS, quickControls);
}
}
}
diff --git a/src/com/android/browser/PhoneUi.java b/src/com/android/browser/PhoneUi.java
index 66656cf..e35e624 100644
--- a/src/com/android/browser/PhoneUi.java
+++ b/src/com/android/browser/PhoneUi.java
@@ -19,6 +19,7 @@ package com.android.browser;
import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
+import android.util.Log;
import android.view.ActionMode;
import android.view.Gravity;
import android.view.Menu;
@@ -118,6 +119,27 @@ public class PhoneUi extends BaseUi {
}
@Override
+ public void setActiveTab(Tab tab) {
+ super.setActiveTab(tab);
+ WebView view = tab.getWebView();
+ // TabControl.setCurrentTab has been called before this,
+ // so the tab is guaranteed to have a webview
+ if (view == null) {
+ Log.e(LOGTAG, "active tab with no webview detected");
+ return;
+ }
+ view.setEmbeddedTitleBar(getEmbeddedTitleBar());
+ if (tab.isInVoiceSearchMode()) {
+ showVoiceTitleBar(tab.getVoiceDisplayTitle());
+ } else {
+ revertVoiceTitleBar(tab);
+ }
+ resetTitleIconAndProgress(tab);
+ updateLockIconToLatest(tab);
+ tab.getTopWindow().requestFocus();
+ }
+
+ @Override
protected void attachFakeTitleBar(WebView mainView) {
WindowManager manager = (WindowManager)
mActivity.getSystemService(Context.WINDOW_SERVICE);
diff --git a/src/com/android/browser/PieControl.java b/src/com/android/browser/PieControl.java
new file mode 100644
index 0000000..210e9ea
--- /dev/null
+++ b/src/com/android/browser/PieControl.java
@@ -0,0 +1,151 @@
+/*
+ * 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 com.android.browser.view.PieMenu;
+
+import android.app.Activity;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup.LayoutParams;
+import android.webkit.WebView;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * controller for Quick Controls pie menu
+ */
+public class PieControl implements OnClickListener, PieMenu.PieController {
+
+ private Activity mActivity;
+ private UiController mUiController;
+ private XLargeUi mUi;
+ private PieMenu mPie;
+ private ImageView mBack;
+ private ImageView mForward;
+ private ImageView mRefresh;
+ private ImageView mUrl;
+ private ImageView mOptions;
+ private ImageView mBookmarks;
+ private ImageView mNewTab;
+ private ImageView mClose;
+
+ private Map<View,Tab> mTabItems;
+
+ boolean mNewTabMode = true;
+
+ public PieControl(Activity activity, UiController controller, XLargeUi ui) {
+ mActivity = activity;
+ mUiController = controller;
+ mUi = ui;
+ mTabItems = new HashMap<View, Tab>();
+ }
+
+ protected void attachToContainer(FrameLayout container) {
+ if (mPie == null) {
+ mPie = new PieMenu(mActivity);
+ LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ mPie.setLayoutParams(lp);
+ mForward = makeMenuView(R.drawable.ic_pie_forward);
+ mPie.addItem(mForward);
+ mRefresh = makeMenuView(R.drawable.ic_pie_refresh);
+ mPie.addItem(mRefresh);
+ mBack = makeMenuView(R.drawable.ic_pie_back);
+ mPie.addItem(mBack);
+ mUrl = makeMenuView(R.drawable.ic_pie_search);
+ mPie.addItem(mUrl);
+ mBookmarks = makeMenuView(R.drawable.ic_pie_bookmarks);
+ mPie.addItem(mBookmarks);
+ mNewTab = makeMenuView(R.drawable.ic_pie_new_tab);
+ mPie.addItem(mNewTab);
+ mOptions = makeMenuView(R.drawable.ic_pie_more);
+ mPie.addItem(mOptions);
+ setClickListener(mBack, mForward, mRefresh, mUrl, mOptions,
+ mBookmarks, mNewTab);
+ mPie.setController(this);
+ }
+ container.addView(mPie);
+ }
+
+ protected void removeFromContainer(FrameLayout container) {
+ container.removeView(mPie);
+ }
+
+ private ImageView makeMenuView(int image) {
+ ImageView item = new ImageView(mActivity);
+ item.setImageResource(image);
+ LayoutParams lp = new LayoutParams(48, 48);
+ item.setLayoutParams(lp);
+ return item;
+ }
+
+ private void setClickListener(View... views) {
+ for (View view : views) {
+ view.setOnClickListener(this);
+ }
+ }
+
+ protected void forceToTop(FrameLayout container) {
+ if (mPie.getParent() != null) {
+ container.removeView(mPie);
+ container.addView(mPie);
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ mPie.show(false);
+ Tab tab = mUiController.getTabControl().getCurrentTab();
+ WebView web = tab.getWebView();
+ if (mBack == v) {
+ web.goBack();
+ } else if (mForward == v) {
+ web.goForward();
+ } else if (mRefresh == v) {
+ if (tab.inPageLoad()) {
+ web.stopLoading();
+ } else {
+ web.reload();
+ }
+ } else if (mUrl == v) {
+ mUi.showFakeTitleBarAndEdit();
+ } else if (mOptions == v) {
+ mActivity.openOptionsMenu();
+ } else if (mBookmarks == v) {
+ mUiController.bookmarksOrHistoryPicker(false);
+ } else if (mNewTab == v) {
+ mUiController.openTabToHomePage();
+ } else if (mClose == v) {
+ mUiController.closeCurrentTab();
+ } else {
+ Tab ntab = mTabItems.get(v);
+ if (ntab != null) {
+ mUiController.switchToTab(mUiController.getTabControl().getTabIndex(ntab));
+ }
+ }
+ }
+
+ @Override
+ public boolean onOpen() {
+ return true;
+ }
+
+}
diff --git a/src/com/android/browser/TabBar.java b/src/com/android/browser/TabBar.java
index 6a139f3..ea734a6 100644
--- a/src/com/android/browser/TabBar.java
+++ b/src/com/android/browser/TabBar.java
@@ -88,6 +88,7 @@ public class TabBar extends LinearLayout
private int mTabOverlap;
private int mTabSliceWidth;
private int mTabPadding;
+ private boolean mUseQuickControls;
public TabBar(Activity activity, UiController controller, XLargeUi ui) {
super(activity);
@@ -139,6 +140,14 @@ public class TabBar extends LinearLayout
mShaderPaint.setAntiAlias(true);
}
+ void setUseQuickControls(boolean useQuickControls) {
+ mUseQuickControls = useQuickControls;
+ }
+
+ int getTabCount() {
+ return mTabMap.size();
+ }
+
void updateTabs(List<Tab> tabs) {
mTabs.clearTabs();
mTabMap.clear();
@@ -182,6 +191,7 @@ public class TabBar extends LinearLayout
if (mNewTab == view) {
mUiController.openTabToHomePage();
} else if (mTabs.getSelectedTab() == view) {
+ if (mUseQuickControls) return;
if (mUi.isFakeTitleBarShowing() && !isLoading()) {
mUi.hideFakeTitleBar();
} else {
@@ -202,7 +212,7 @@ public class TabBar extends LinearLayout
mUserRequestedUrlbar = true;
}
- private void showTitleBarIndicator(boolean show) {
+ void showTitleBarIndicator(boolean show) {
Tab tab = mTabControl.getCurrentTab();
if (tab != null) {
TabViewData tvd = mTabMap.get(tab);
@@ -229,6 +239,7 @@ public class TabBar extends LinearLayout
@Override
public void onScroll(int visibleTitleHeight) {
+ if (mUseQuickControls) return;
// isLoading is using the current tab, which initially might not be set yet
if (mTabControl.getCurrentTab() != null) {
if ((mVisibleTitleHeight != 0) && (visibleTitleHeight == 0)
@@ -417,7 +428,7 @@ public class TabBar extends LinearLayout
int[] pos = new int[2];
getLocationInWindow(mWindowPos);
Drawable drawable = mSelected ? mActiveDrawable : mInactiveDrawable;
- drawable.setBounds(0, 0, mUi.getTitleBarWidth(), getHeight());
+ drawable.setBounds(0, 0, mUi.getContentWidth(), getHeight());
drawClipped(canvas, drawable, mPath, mWindowPos[0]);
canvas.restoreToCount(state);
super.dispatchDraw(canvas);
diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java
index b680512..f1c6c6b 100644
--- a/src/com/android/browser/TitleBarXLarge.java
+++ b/src/com/android/browser/TitleBarXLarge.java
@@ -73,6 +73,7 @@ public class TitleBarXLarge extends TitleBarBase
private boolean mInLoad;
private boolean mEditable;
+ private boolean mUseQuickControls;
public TitleBarXLarge(Activity activity, UiController controller,
XLargeUi ui) {
@@ -140,6 +141,29 @@ public class TitleBarXLarge extends TitleBarBase
}
}
+ void setUseQuickControls(boolean useQuickControls) {
+ mUseQuickControls = useQuickControls;
+ if (mUseQuickControls) {
+ mBackButton.setVisibility(View.GONE);
+ mForwardButton.setVisibility(View.GONE);
+ mStopButton.setVisibility(View.GONE);
+ mAllButton.setVisibility(View.GONE);
+ } else {
+ mBackButton.setVisibility(View.VISIBLE);
+ mForwardButton.setVisibility(View.VISIBLE);
+ mStopButton.setVisibility(View.VISIBLE);
+ mAllButton.setVisibility(View.VISIBLE);
+ }
+ }
+
+ void setShowProgressOnly(boolean progress) {
+ if (progress) {
+ mContainer.setVisibility(View.GONE);
+ } else {
+ mContainer.setVisibility(View.VISIBLE);
+ }
+ }
+
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (!mEditable && hasFocus) {
@@ -291,12 +315,16 @@ public class TitleBarXLarge extends TitleBarBase
updateSearchMode();
} else {
mUrlInput.clearFocus();
- mSearchButton.setVisibility(View.VISIBLE);
mGoButton.setVisibility(View.GONE);
mVoiceSearch.setVisibility(View.GONE);
mStar.setVisibility(View.VISIBLE);
mClearButton.setVisibility(View.GONE);
mVoiceSearchIndicator.setVisibility(View.GONE);
+ if (mUseQuickControls) {
+ mSearchButton.setVisibility(View.GONE);
+ } else {
+ mSearchButton.setVisibility(View.VISIBLE);
+ }
}
}
diff --git a/src/com/android/browser/XLargeUi.java b/src/com/android/browser/XLargeUi.java
index 69e6724..7f9baa7 100644
--- a/src/com/android/browser/XLargeUi.java
+++ b/src/com/android/browser/XLargeUi.java
@@ -21,8 +21,12 @@ import com.android.browser.ScrollWebView.ScrollListener;
import android.app.ActionBar;
import android.app.Activity;
import android.graphics.Bitmap;
+import android.util.Log;
import android.view.ActionMode;
+import android.view.Gravity;
import android.webkit.WebView;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
import java.util.List;
@@ -33,11 +37,15 @@ public class XLargeUi extends BaseUi implements ScrollListener {
private static final String LOGTAG = "XLargeUi";
+ private ActionBar mActionBar;
private TabBar mTabBar;
private TitleBarXLarge mTitleBar;
private TitleBarXLarge mFakeTitleBar;
+ private boolean mUseQuickControls;
+ private PieControl mPieControl;
+
/**
* @param browser
* @param controller
@@ -49,10 +57,51 @@ public class XLargeUi extends BaseUi implements ScrollListener {
mTitleBar.setEditable(false);
mFakeTitleBar = new TitleBarXLarge(mActivity, mUiController, this);
mFakeTitleBar.setEditable(true);
- ActionBar actionBar = mActivity.getActionBar();
mTabBar = new TabBar(mActivity, mUiController, this);
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
- actionBar.setCustomView(mTabBar);
+ mActionBar = mActivity.getActionBar();
+ mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
+ mActionBar.setCustomView(mTabBar);
+ setUseQuickControls(BrowserSettings.getInstance().useQuickControls());
+ }
+
+ private void setUseQuickControls(boolean useQuickControls) {
+ mUseQuickControls = useQuickControls;
+ if (useQuickControls) {
+ checkTabCount();
+ mPieControl = new PieControl(mActivity, mUiController, this);
+ mPieControl.attachToContainer(mContentView);
+ setFakeTitleBarGravity(Gravity.BOTTOM);
+
+ // remove embedded title bar if present
+ WebView web = mTabControl.getCurrentWebView();
+ if ((web != null) && (web.getVisibleTitleHeight() > 0)) {
+ web.setEmbeddedTitleBar(null);
+ }
+ } else {
+ mActivity.getActionBar().show();
+ if (mPieControl != null) {
+ mPieControl.removeFromContainer(mContentView);
+ }
+ setFakeTitleBarGravity(Gravity.TOP);
+ // remove embedded title bar if present
+ WebView web = mTabControl.getCurrentWebView();
+ if ((web != null) && (web.getVisibleTitleHeight() == 0)) {
+ web.setEmbeddedTitleBar(mTitleBar);
+ }
+ }
+ mTabBar.setUseQuickControls(mUseQuickControls);
+ mFakeTitleBar.setUseQuickControls(mUseQuickControls);
+ }
+
+ private void checkTabCount() {
+ if (mUseQuickControls) {
+ int n = mTabBar.getTabCount();
+ if (n >= 2) {
+ mActivity.getActionBar().show();
+ } else if (n == 1) {
+ mActivity.getActionBar().hide();
+ }
+ }
}
@Override
@@ -114,6 +163,9 @@ public class XLargeUi extends BaseUi implements ScrollListener {
public void onPageFinished(Tab tab, String url) {
mTabBar.onPageFinished(tab);
super.onPageFinished(tab, url);
+ if (mUseQuickControls) {
+ mFakeTitleBar.setShowProgressOnly(false);
+ }
}
@Override
@@ -123,7 +175,17 @@ public class XLargeUi extends BaseUi implements ScrollListener {
mFakeTitleBar.setProgress(progress);
if (progress == 100) {
hideFakeTitleBar();
+ if (mUseQuickControls) {
+ mFakeTitleBar.setShowProgressOnly(false);
+ setFakeTitleBarGravity(Gravity.BOTTOM);
+ }
} else {
+ if (mUseQuickControls) {
+ mFakeTitleBar.setShowProgressOnly(true);
+ if (!isFakeTitleBarShowing()) {
+ setFakeTitleBarGravity(Gravity.TOP);
+ }
+ }
showFakeTitleBar();
}
}
@@ -137,28 +199,55 @@ public class XLargeUi extends BaseUi implements ScrollListener {
@Override
public void addTab(Tab tab) {
mTabBar.onNewTab(tab);
+ checkTabCount();
}
@Override
public void setActiveTab(Tab tab) {
super.setActiveTab(tab);
+ ScrollWebView view = (ScrollWebView) tab.getWebView();
+ // TabControl.setCurrentTab has been called before this,
+ // so the tab is guaranteed to have a webview
+ if (view == null) {
+ Log.e(LOGTAG, "active tab with no webview detected");
+ return;
+ }
+ // Request focus on the top window.
+ if (mUseQuickControls) {
+ mPieControl.forceToTop(mContentView);
+ view.setScrollListener(null);
+ mTabBar.showTitleBarIndicator(false);
+ } else {
+ view.setEmbeddedTitleBar(mTitleBar);
+ view.setScrollListener(this);
+ }
mTabBar.onSetActiveTab(tab);
+ if (tab.isInVoiceSearchMode()) {
+ showVoiceTitleBar(tab.getVoiceDisplayTitle());
+ } else {
+ revertVoiceTitleBar(tab);
+ }
+ resetTitleIconAndProgress(tab);
+ updateLockIconToLatest(tab);
+ tab.getTopWindow().requestFocus();
}
@Override
public void updateTabs(List<Tab> tabs) {
mTabBar.updateTabs(tabs);
+ checkTabCount();
}
@Override
public void removeTab(Tab tab) {
super.removeTab(tab);
mTabBar.onRemoveTab(tab);
+ checkTabCount();
}
- int getTitleBarWidth() {
- if (mTitleBar != null) {
- return mTitleBar.getWidth();
+ int getContentWidth() {
+ if (mContentView != null) {
+ return mContentView.getWidth();
}
return 0;
}
@@ -168,6 +257,22 @@ public class XLargeUi extends BaseUi implements ScrollListener {
mFakeTitleBar.onEditUrl(clearInput);
}
+ void setFakeTitleBarGravity(int gravity) {
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams)
+ mFakeTitleBar.getLayoutParams();
+ if (lp == null) {
+ lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ }
+ lp.gravity = gravity;
+ mFakeTitleBar.setLayoutParams(lp);
+ }
+
+ void showFakeTitleBarAndEdit() {
+ showFakeTitleBar();
+ mFakeTitleBar.onEditUrl(false);
+ }
+
@Override
protected void attachFakeTitleBar(WebView mainView) {
mContentView.addView(mFakeTitleBar);
@@ -208,6 +313,20 @@ public class XLargeUi extends BaseUi implements ScrollListener {
}
@Override
+ public void onActionModeFinished(boolean inLoad) {
+ checkTabCount();
+ if (inLoad) {
+ // the titlebar was removed when the CAB was shown
+ // if the page is loading, show it again
+ mFakeTitleBar.setShowProgressOnly(true);
+ if (!isFakeTitleBarShowing()) {
+ setFakeTitleBarGravity(Gravity.TOP);
+ }
+ showFakeTitleBar();
+ }
+ }
+
+ @Override
public void setUrlTitle(Tab tab, String url, String title) {
super.setUrlTitle(tab, url, title);
mTabBar.onUrlAndTitle(tab, url, title);
diff --git a/src/com/android/browser/preferences/LabPreferencesFragment.java b/src/com/android/browser/preferences/LabPreferencesFragment.java
new file mode 100644
index 0000000..8a8546f
--- /dev/null
+++ b/src/com/android/browser/preferences/LabPreferencesFragment.java
@@ -0,0 +1,58 @@
+/*
+ * 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.preferences;
+
+import com.android.browser.BrowserActivity;
+import com.android.browser.BrowserSettings;
+import com.android.browser.Controller;
+import com.android.browser.R;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceActivity.Header;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager.OnActivityResultListener;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+public class LabPreferencesFragment extends PreferenceFragment
+ implements OnPreferenceChangeListener {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the XML preferences file
+ addPreferencesFromResource(R.xml.lab_preferences);
+
+ Preference e = findPreference(BrowserSettings.PREF_QUICK_CONTROLS);
+ e.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ // Attempt to restart
+ startActivity(new Intent(BrowserActivity.ACTION_RESTART, null,
+ getActivity(), BrowserActivity.class));
+ return true;
+ }
+
+}
diff --git a/src/com/android/browser/view/PieMenu.java b/src/com/android/browser/view/PieMenu.java
new file mode 100644
index 0000000..d838a34
--- /dev/null
+++ b/src/com/android/browser/view/PieMenu.java
@@ -0,0 +1,463 @@
+/*
+ * 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.view;
+
+import com.android.browser.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PieMenu extends FrameLayout {
+
+ private static final int RADIUS_GAP = 10;
+
+ public interface PieController {
+ /**
+ * called before menu opens to customize menu
+ * returns if pie state has been changed
+ */
+ public boolean onOpen();
+ }
+ private Point mCenter;
+ private int mRadius;
+ private int mRadiusInc;
+ private int mSlop;
+
+ private boolean mOpen;
+ private Paint mPaint;
+ private Paint mSelectedPaint;
+ private PieController mController;
+
+ private Map<View, List<View>> mMenu;
+ private List<View> mStack;
+
+ private boolean mDirty;
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public PieMenu(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public PieMenu(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ /**
+ * @param context
+ */
+ public PieMenu(Context context) {
+ super(context);
+ init(context);
+ }
+
+ private void init(Context ctx) {
+ this.setTag(new MenuTag(0));
+ mStack = new ArrayList<View>();
+ mStack.add(this);
+ Resources res = ctx.getResources();
+ mRadius = (int) res.getDimension(R.dimen.qc_radius);
+ mRadiusInc = (int) res.getDimension(R.dimen.qc_radius_inc);
+ mSlop = (int) res.getDimension(R.dimen.qc_slop);
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setColor(res.getColor(R.color.qc_slice_normal));
+ mSelectedPaint = new Paint();
+ mSelectedPaint.setAntiAlias(true);
+ mSelectedPaint.setColor(res.getColor(R.color.qc_slice_active));
+ mOpen = false;
+ mMenu = new HashMap<View, List<View>>();
+ setWillNotDraw(false);
+ setDrawingCacheEnabled(false);
+ mCenter = new Point(0,0);
+ mDirty = true;
+ }
+
+ public void setController(PieController ctl) {
+ mController = ctl;
+ }
+
+ public void setRadius(int r) {
+ mRadius = r;
+ requestLayout();
+ }
+
+ public void setRadiusIncrement(int ri) {
+ mRadiusInc = ri;
+ requestLayout();
+ }
+
+ /**
+ * add a menu item to another item as a submenu
+ * @param item
+ * @param parent
+ */
+ public void addItem(View item, View parent) {
+ List<View> subs = mMenu.get(parent);
+ if (subs == null) {
+ subs = new ArrayList<View>();
+ mMenu.put(parent, subs);
+ }
+ subs.add(item);
+ MenuTag tag = new MenuTag(((MenuTag) parent.getTag()).level + 1);
+ item.setTag(tag);
+ }
+
+ public void addItem(View view) {
+ // add the item to the pie itself
+ addItem(view, this);
+ }
+
+ public void removeItem(View view) {
+ List<View> subs = mMenu.get(view);
+ mMenu.remove(view);
+ for (View p : mMenu.keySet()) {
+ List<View> sl = mMenu.get(p);
+ if (sl != null) {
+ sl.remove(view);
+ }
+ }
+ }
+
+ public void clearItems(View parent) {
+ List<View> subs = mMenu.remove(parent);
+ if (subs != null) {
+ for (View sub: subs) {
+ clearItems(sub);
+ }
+ }
+ }
+
+ public void clearItems() {
+ mMenu.clear();
+ }
+
+
+ public void show(boolean show) {
+ mOpen = show;
+ if (mOpen) {
+ if (mController != null) {
+ boolean changed = mController.onOpen();
+ }
+ mDirty = true;
+ }
+ if (!show) {
+ // hide sub items
+ mStack.clear();
+ mStack.add(this);
+ }
+ invalidate();
+ }
+
+ private void setCenter(int x, int y) {
+ if (x < mSlop) {
+ mCenter.x = 0;
+ } else {
+ mCenter.x = getWidth();
+ }
+ mCenter.y = y;
+ }
+
+ private boolean onTheLeft() {
+ return mCenter.x < mSlop;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mOpen) {
+ int radius = mRadius;
+ // start in the center for 0 level menu
+ float anchor = (float) Math.PI / 2;
+ PointF angles = new PointF();
+ int state = canvas.save();
+ if (onTheLeft()) {
+ // left handed
+ canvas.scale(-1, 1);
+ }
+ for (View parent : mStack) {
+ List<View> subs = mMenu.get(parent);
+ if (subs != null) {
+ setGeometry(anchor, subs.size(), angles);
+ }
+ anchor = drawSlices(canvas, subs, radius, angles.x, angles.y);
+ radius += mRadiusInc + RADIUS_GAP;
+ }
+ canvas.restoreToCount(state);
+ mDirty = false;
+ }
+ }
+
+ /**
+ * draw the set of slices
+ * @param canvas
+ * @param items
+ * @param radius
+ * @param start
+ * @param sweep
+ * @return the angle of the selected slice
+ */
+ private float drawSlices(Canvas canvas, List<View> items, int radius,
+ float start, float sweep) {
+ float angle = start + sweep / 2;
+ // gap between slices in degrees
+ float gap = 1f;
+ float newanchor = 0f;
+ for (View item : items) {
+ if (mDirty) {
+ item.measure(item.getLayoutParams().width,
+ item.getLayoutParams().height);
+ int w = item.getMeasuredWidth();
+ int h = item.getMeasuredHeight();
+ int x = (int) (radius * Math.sin(angle));
+ int y = mCenter.y - (int) (radius * Math.cos(angle)) - h / 2;
+ if (onTheLeft()) {
+ x = mCenter.x + x - w / 2;
+ } else {
+ x = mCenter.x - x - w / 2;
+ }
+ item.layout(x, y, x + w, y + h);
+ }
+ float itemstart = angle - sweep / 2;
+ int inner = radius - mRadiusInc / 2;
+ int outer = radius + mRadiusInc / 2;
+ Path slice = makeSlice(getDegrees(itemstart) - gap,
+ getDegrees(itemstart + sweep) + gap,
+ outer, inner, mCenter);
+ MenuTag tag = (MenuTag) item.getTag();
+ tag.start = itemstart;
+ tag.sweep = sweep;
+ tag.inner = inner;
+ tag.outer = outer;
+
+ Paint p = item.isPressed() ? mSelectedPaint : mPaint;
+ canvas.drawPath(slice, p);
+ int state = canvas.save();
+ if (onTheLeft()) {
+ canvas.scale(-1, 1);
+ }
+ canvas.translate(item.getX(), item.getY());
+ item.draw(canvas);
+ canvas.restoreToCount(state);
+ if (mStack.contains(item)) {
+ // item is anchor for sub menu
+ newanchor = angle;
+ }
+ angle += sweep;
+ }
+ return newanchor;
+ }
+
+ /**
+ * converts a
+ * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
+ * @return skia angle
+ */
+ private float getDegrees(double angle) {
+ return (float) (270 - 180 * angle / Math.PI);
+ }
+
+ private Path makeSlice(float startangle, float endangle, int outerradius,
+ int innerradius, Point center) {
+ RectF bb = new RectF(center.x - outerradius, center.y - outerradius,
+ center.x + outerradius, center.y + outerradius);
+ RectF bbi = new RectF(center.x - innerradius, center.y - innerradius,
+ center.x + innerradius, center.y + innerradius);
+ Path path = new Path();
+ path.arcTo(bb, startangle, endangle - startangle, true);
+ path.arcTo(bbi, endangle, startangle - endangle);
+ path.close();
+ return path;
+ }
+
+ /**
+ * all angles are 0 .. MATH.PI where 0 points up, and rotate counterclockwise
+ * set the startangle and slice sweep in result
+ * @param anchorangle : angle at which the menu is anchored
+ * @param nslices
+ * @param result : x : start, y : sweep
+ */
+ private void setGeometry(float anchorangle, int nslices, PointF result) {
+ float span = (float) Math.min(anchorangle, Math.PI - anchorangle);
+ float sweep = 2 * span / (nslices + 1);
+ result.x = anchorangle - span + sweep / 2;
+ result.y = sweep;
+ }
+
+ // touch handling for pie
+
+ View mCurrentView;
+ Rect mHitRect = new Rect();
+
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ float x = evt.getX();
+ float y = evt.getY();
+ int action = evt.getActionMasked();
+ int edges = evt.getEdgeFlags();
+ if (MotionEvent.ACTION_DOWN == action) {
+ if ((x > getWidth() - mSlop) || (x < mSlop)) {
+ setCenter((int) x, (int) y);
+ show(true);
+ return true;
+ }
+ } else if (MotionEvent.ACTION_UP == action) {
+ if (mOpen) {
+ View v = mCurrentView;
+ deselect();
+ if (v != null) {
+ v.performClick();
+ }
+ show(false);
+ return true;
+ }
+ } else if (MotionEvent.ACTION_CANCEL == action) {
+ if (mOpen) {
+ show(false);
+ }
+ deselect();
+ return false;
+ } else if (MotionEvent.ACTION_MOVE == action) {
+ View v = findView((int) x, (int) y);
+ if (mCurrentView != v) {
+ onEnter(v);
+ invalidate();
+ }
+ }
+ // always re-dispatch event
+ return false;
+ }
+
+ /**
+ * enter a slice for a view
+ * updates model only
+ * @param view
+ */
+ private void onEnter(View view) {
+ // deselect
+ if (mCurrentView != null) {
+ if (getLevel(mCurrentView) >= getLevel(view)) {
+ mCurrentView.setPressed(false);
+ }
+ }
+ if (view != null) {
+ // clear up stack
+ MenuTag tag = (MenuTag) view.getTag();
+ int i = mStack.size() - 1;
+ while (i > 0) {
+ View v = mStack.get(i);
+ if (((MenuTag) v.getTag()).level >= tag.level) {
+ v.setPressed(false);
+ mStack.remove(i);
+ } else {
+ break;
+ }
+ i--;
+ }
+ List<View> items = mMenu.get(view);
+ if (items != null) {
+ mStack.add(view);
+ mDirty = true;
+ }
+ view.setPressed(true);
+ }
+ mCurrentView = view;
+ }
+
+ private void deselect() {
+ if (mCurrentView != null) {
+ mCurrentView.setPressed(false);
+ }
+ mCurrentView = null;
+ }
+
+ private int getLevel(View v) {
+ if (v == null) return -1;
+ return ((MenuTag) v.getTag()).level;
+ }
+
+ private View findView(int x, int y) {
+ // get angle and radius from x/y
+ float angle = (float) Math.PI / 2;
+ x = mCenter.x - x;
+ if (mCenter.x < mSlop) {
+ x = -x;
+ }
+ y = mCenter.y - y;
+ float dist = (float) Math.sqrt(x * x + y * y);
+ if (y > 0) {
+ angle = (float) Math.asin(x / dist);
+ } else if (y < 0) {
+ angle = (float) (Math.PI - Math.asin(x / dist ));
+ }
+ // find the matching item:
+ for (View parent : mStack) {
+ List<View> subs = mMenu.get(parent);
+ if (subs != null) {
+ for (View item : subs) {
+ MenuTag tag = (MenuTag) item.getTag();
+ if ((tag.inner < dist)
+ && (tag.outer > dist)
+ && (tag.start < angle)
+ && (tag.start + tag.sweep > angle)) {
+ return item;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ class MenuTag {
+
+ int level;
+ float start;
+ float sweep;
+ int inner;
+ int outer;
+
+ public MenuTag(int l) {
+ level = l;
+ }
+
+ }
+
+}