summaryrefslogtreecommitdiffstats
path: root/src/com/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/browser/BaseUi.java22
-rw-r--r--src/com/android/browser/BrowserBookmarksPage.java2
-rw-r--r--src/com/android/browser/BrowserSettings.java39
-rw-r--r--src/com/android/browser/Controller.java110
-rw-r--r--src/com/android/browser/DeviceAccountLogin.java143
-rw-r--r--src/com/android/browser/GoogleAccountLogin.java74
-rw-r--r--src/com/android/browser/InstantSearchEngine.java10
-rw-r--r--src/com/android/browser/IntentHandler.java16
-rw-r--r--src/com/android/browser/PieControl.java308
-rw-r--r--src/com/android/browser/ScrollWebView.java43
-rw-r--r--src/com/android/browser/SuggestionsAdapter.java8
-rw-r--r--src/com/android/browser/Tab.java44
-rw-r--r--src/com/android/browser/TitleBarXLarge.java162
-rw-r--r--src/com/android/browser/UI.java7
-rw-r--r--src/com/android/browser/UiController.java4
-rw-r--r--src/com/android/browser/UrlInputView.java4
-rw-r--r--src/com/android/browser/WebViewController.java3
-rw-r--r--src/com/android/browser/XLargeUi.java238
-rw-r--r--src/com/android/browser/autocomplete/SuggestiveAutoCompleteTextView.java15
-rw-r--r--src/com/android/browser/preferences/GeneralPreferencesFragment.java234
-rw-r--r--src/com/android/browser/preferences/ImportWizard.java491
-rw-r--r--src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java56
-rw-r--r--src/com/android/browser/provider/BrowserProvider2.java3
-rw-r--r--src/com/android/browser/view/BasePieView.java161
-rw-r--r--src/com/android/browser/view/EventRedirectingFrameLayout.java74
-rw-r--r--src/com/android/browser/view/PieItem.java109
-rw-r--r--src/com/android/browser/view/PieListView.java79
-rw-r--r--src/com/android/browser/view/PieMenu.java513
-rw-r--r--src/com/android/browser/view/PieStackView.java105
29 files changed, 2144 insertions, 933 deletions
diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java
index c01ec06..71346ae 100644
--- a/src/com/android/browser/BaseUi.java
+++ b/src/com/android/browser/BaseUi.java
@@ -240,6 +240,7 @@ public abstract class BaseUi implements UI, WebViewFactory {
onProgressChanged(tab);
boolean incognito = mActiveTab.getWebView().isPrivateBrowsingEnabled();
getTitleBar().setIncognitoMode(incognito);
+ updateAutoLogin(tab, false);
}
Tab getActiveTab() {
@@ -468,6 +469,10 @@ public abstract class BaseUi implements UI, WebViewFactory {
mContentView.addView(mComboView, COVER_SCREEN_PARAMS);
}
+ public boolean isComboViewShowing() {
+ return (mComboView != null);
+ }
+
/**
* dismiss the ComboPage
*/
@@ -542,11 +547,23 @@ public abstract class BaseUi implements UI, WebViewFactory {
&& mComboView == null;
}
+ @Override
+ public void showAutoLogin(Tab tab) {
+ updateAutoLogin(tab, true);
+ }
+
+ @Override
+ public void hideAutoLogin(Tab tab) {
+ updateAutoLogin(tab, true);
+ }
+
// -------------------------------------------------------------------------
protected void updateNavigationState(Tab tab) {
}
+ protected void updateAutoLogin(Tab tab, boolean animate) {}
+
/**
* Update the lock icon to correspond to our latest state.
*/
@@ -612,6 +629,11 @@ public abstract class BaseUi implements UI, WebViewFactory {
// menu handling callbacks
@Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ return true;
+ }
+
+ @Override
public void onOptionsMenuOpened() {
}
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index 98b25a8..de28d0d 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -453,7 +453,7 @@ public class BrowserBookmarksPage extends Fragment implements View.OnCreateConte
}
private void loadUrl(int position) {
- if (mCallbacks != null) {
+ if (mCallbacks != null && mAdapter != null) {
mCallbacks.onBookmarkSelected(mAdapter.getItem(position), false);
}
}
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 75e0cfb..357d1e9 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -92,9 +92,6 @@ public class BrowserSettings extends Observable implements OnSharedPreferenceCha
private String databasePath; // default value set in loadFromDb()
private String geolocationDatabasePath; // default value set in loadFromDb()
private WebStorageSizeManager webStorageSizeManager;
- // Autologin settings
- private boolean autoLoginEnabled;
- private String autoLoginAccount;
private String jsFlags = "";
@@ -168,8 +165,6 @@ public class BrowserSettings extends Observable implements OnSharedPreferenceCha
public final static String PREF_QUICK_CONTROLS = "enable_quick_controls";
public final static String PREF_MOST_VISITED_HOMEPAGE = "use_most_visited_homepage";
- public final static String PREF_AUTOLOGIN = "enable_autologin";
- public final static String PREF_AUTOLOGIN_ACCOUNT = "autologin_account";
public final static String PREF_PLUGIN_STATE = "plugin_state";
public final static String PREF_USE_INSTANT = "use_instant_search";
@@ -537,11 +532,6 @@ public class BrowserSettings extends Observable implements OnSharedPreferenceCha
geolocationEnabled = p.getBoolean("enable_geolocation", geolocationEnabled);
workersEnabled = p.getBoolean("enable_workers", workersEnabled);
- // Autologin account settings. The account preference may be null until
- // the user explicitly changes the account in the settings.
- autoLoginEnabled = p.getBoolean(PREF_AUTOLOGIN, autoLoginEnabled);
- autoLoginAccount = p.getString(PREF_AUTOLOGIN_ACCOUNT, autoLoginAccount);
-
update();
}
@@ -632,32 +622,6 @@ public class BrowserSettings extends Observable implements OnSharedPreferenceCha
update();
}
- public boolean isAutoLoginEnabled() {
- return autoLoginEnabled;
- }
-
- public String getAutoLoginAccount(Context context) {
- // Each time we attempt to get the account, we need to verify that the
- // account is still valid.
- return GoogleAccountLogin.validateAccount(context, autoLoginAccount);
- }
-
- public void setAutoLoginAccount(Context context, String name) {
- Editor ed = PreferenceManager.
- getDefaultSharedPreferences(context).edit();
- ed.putString(PREF_AUTOLOGIN_ACCOUNT, name);
- ed.apply();
- autoLoginAccount = name;
- }
-
- public void setAutoLoginEnabled(Context context, boolean enable) {
- Editor ed = PreferenceManager.
- getDefaultSharedPreferences(context).edit();
- ed.putBoolean(PREF_AUTOLOGIN, enable);
- ed.apply();
- autoLoginEnabled = enable;
- }
-
public void setAutoFillProfile(Context ctx, AutoFillProfile profile, Message msg) {
if (profile != null) {
setActiveAutoFillProfileId(ctx, profile.getUniqueId());
@@ -854,9 +818,6 @@ public class BrowserSettings extends Observable implements OnSharedPreferenceCha
domStorageEnabled = true;
geolocationEnabled = true;
workersEnabled = true; // only affects V8. JSC does not have a similar setting
- // Autologin default is true. The account will be populated when
- // reading from the DB as that is when a context is available.
- autoLoginEnabled = true;
}
private abstract class AutoFillProfileDbTask<T> extends AsyncTask<T, Void, Void> {
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 9f614b0..a028882 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -161,9 +161,6 @@ public class Controller
private int mOldMenuState = EMPTY_MENU;
private Menu mCachedMenu;
- // Used to prevent chording to result in firing two shortcuts immediately
- // one after another. Fixes bug 1211714.
- boolean mCanChord;
private boolean mMenuIsDown;
// For select and find, we keep track of the ActionMode so that
@@ -279,7 +276,7 @@ public class Controller
CookieManager.getInstance().removeSessionCookie();
}
- GoogleAccountLogin.startLoginIfNeeded(mActivity, mSettings,
+ GoogleAccountLogin.startLoginIfNeeded(mActivity,
new Runnable() {
@Override public void run() {
start(icicle, intent, currentTab, restoreIncognitoTabs);
@@ -996,6 +993,19 @@ public class Controller
mPageDialogsHandler.showSSLCertificateOnError(view, handler, error);
}
+ @Override
+ public void showAutoLogin(Tab tab) {
+ assert tab.inForeground();
+ // Update the title bar to show the auto-login request.
+ mUi.showAutoLogin(tab);
+ }
+
+ @Override
+ public void hideAutoLogin(Tab tab) {
+ assert tab.inForeground();
+ mUi.hideAutoLogin(tab);
+ }
+
// helper method
/*
@@ -1412,9 +1422,6 @@ public class Controller
if (mOptionsMenuHandler != null) {
return mOptionsMenuHandler.onPrepareOptionsMenu(menu);
}
- // This happens when the user begins to hold down the menu key, so
- // allow them to chord to get a shortcut.
- mCanChord = true;
// Note: setVisible will decide whether an item is visible; while
// setEnabled() will decide whether an item is enabled, which also means
// whether the matching shortcut key will function.
@@ -1479,7 +1486,7 @@ public class Controller
break;
}
mCurrentMenuState = mMenuState;
- return true;
+ return mUi.onPrepareOptionsMenu(menu);
}
public boolean onOptionsItemSelected(MenuItem item) {
@@ -1493,11 +1500,6 @@ public class Controller
// if main menu option is selected
removeComboView();
}
- if (!mCanChord) {
- // The user has already fired a shortcut with this hold down of the
- // menu key.
- return false;
- }
if (null == getCurrentTopWebView()) {
return false;
}
@@ -1613,7 +1615,8 @@ public class Controller
new Thread("Add WebArchive to download manager") {
@Override
public void run() {
- manager.completedDownload(null == title ? value : title,
+ manager.addCompletedDownload(
+ null == title ? value : title,
value, true, "application/x-webarchive-xml",
value, length, true);
}
@@ -1640,7 +1643,6 @@ public class Controller
case R.id.share_page_menu_id:
Tab currentTab = mTabControl.getCurrentTab();
if (null == currentTab) {
- mCanChord = false;
return false;
}
shareCurrentPage(currentTab);
@@ -1692,7 +1694,6 @@ public class Controller
default:
return false;
}
- mCanChord = false;
return true;
}
@@ -1702,9 +1703,6 @@ public class Controller
return false;
}
- // chording is not an issue with context menus, but we use the same
- // options selector, so set mCanChord to true so we can access them.
- mCanChord = true;
int id = item.getItemId();
boolean result = true;
switch (id) {
@@ -1743,7 +1741,6 @@ public class Controller
// For other context menus
result = onOptionsItemSelected(item);
}
- mCanChord = false;
return result;
}
@@ -1930,6 +1927,13 @@ public class Controller
R.dimen.bookmarkThumbnailHeight);
}
+ static Bitmap createScreenshot(Tab tab, int width, int height) {
+ if ((tab != null) && (tab.getWebView() != null)) {
+ return createScreenshot(tab.getWebView(), width, height);
+ }
+ return null;
+ }
+
private static Bitmap createScreenshot(WebView view, int width, int height) {
// We render to a bitmap 2x the desired size so that we can then
// re-scale it with filtering since canvas.scale doesn't filter
@@ -1948,17 +1952,17 @@ public class Controller
int thumbnailWidth = thumbnail.getWidth();
int thumbnailHeight = thumbnail.getHeight();
float scaleFactor = 1.0f;
- if (thumbnailWidth > 0) {
+ if (thumbnailWidth > 0 && thumbnailHeight > 0) {
scaleFactor = (float) width / (float)thumbnailWidth;
} else {
return null;
}
- if (view.getWidth() > view.getHeight() &&
- thumbnailHeight < view.getHeight() && thumbnailHeight > 0) {
- // If the device is in landscape and the page is shorter
- // than the height of the view, center the thumnail and crop the sides
- scaleFactor = (float) height / (float)thumbnailHeight;
+ float scaleFactorY = (float) height / (float)thumbnailHeight;
+ if (scaleFactorY > scaleFactor) {
+ // The picture is narrower than the requested AR
+ // Center the thumnail and crop the sides
+ scaleFactor = scaleFactorY;
float wx = (thumbnailWidth * scaleFactor) - width;
canvas.translate((int) -(wx / 2), 0);
}
@@ -2208,17 +2212,9 @@ public class Controller
// animation behavior.
addTab(tab);
setActiveTab(tab);
-
- // Callback to load the url data.
- final Runnable load = new Runnable() {
- @Override public void run() {
- if (!urlData.isEmpty()) {
- loadUrlDataIn(tab, urlData);
- }
- }
- };
-
- GoogleAccountLogin.startLoginIfNeeded(mActivity, mSettings, load);
+ if (!urlData.isEmpty()) {
+ loadUrlDataIn(tab, urlData);
+ }
return tab;
} else {
// Get rid of the subwindow if it exists
@@ -2446,6 +2442,23 @@ public class Controller
}
/**
+ * helper method for key handler
+ * returns the current tab if it can't advance
+ */
+ private int getNextTabIndex() {
+ return Math.min(mTabControl.getTabCount() - 1,
+ mTabControl.getCurrentIndex() + 1);
+ }
+
+ /**
+ * helper method for key handler
+ * returns the current tab if it can't advance
+ */
+ private int getPrevTabIndex() {
+ return Math.max(0, mTabControl.getCurrentIndex() - 1);
+ }
+
+ /**
* handle key events in browser
*
* @param keyCode
@@ -2472,10 +2485,18 @@ public class Controller
boolean shift = event.hasModifiers(KeyEvent.META_SHIFT_ON);
switch(keyCode) {
- case KeyEvent.KEYCODE_ESCAPE:
- if (!noModifiers) break;
- stopLoading();
- return true;
+ case KeyEvent.KEYCODE_TAB:
+ if (event.isCtrlPressed()) {
+ if (event.isShiftPressed()) {
+ // prev tab
+ switchToTab(getPrevTabIndex());
+ } else {
+ // next tab
+ switchToTab(getNextTabIndex());
+ }
+ return true;
+ }
+ break;
case KeyEvent.KEYCODE_SPACE:
// WebView/WebTextView handle the keys in the KeyDown. As
// the Activity's shortcut keys are only handled when WebView
@@ -2545,7 +2566,12 @@ public class Controller
break;
// case KeyEvent.KEYCODE_U: // in Chrome: opens source of page
// case KeyEvent.KEYCODE_V: // text view intercepts to paste
-// case KeyEvent.KEYCODE_W: // menu
+ case KeyEvent.KEYCODE_W: // in Chrome: close tab
+ if (ctrl) {
+ closeCurrentTab();
+ return true;
+ }
+ break;
// case KeyEvent.KEYCODE_X: // text view intercepts to cut
// case KeyEvent.KEYCODE_Y: // unused
// case KeyEvent.KEYCODE_Z: // unused
diff --git a/src/com/android/browser/DeviceAccountLogin.java b/src/com/android/browser/DeviceAccountLogin.java
new file mode 100644
index 0000000..8d734c2
--- /dev/null
+++ b/src/com/android/browser/DeviceAccountLogin.java
@@ -0,0 +1,143 @@
+/*
+ * 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 com.android.browser;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.app.Activity;
+import android.os.Bundle;
+import android.webkit.WebView;
+
+public class DeviceAccountLogin implements
+ AccountManagerCallback<Bundle> {
+
+ private final Activity mActivity;
+ private final WebView mWebView;
+ private final Tab mTab;
+ private final WebViewController mWebViewController;
+ private final AccountManager mAccountManager;
+ Account[] mAccounts;
+ private AutoLoginCallback mCallback;
+ private String mAuthToken;
+
+ // Current state of the login.
+ private int mState = INITIAL;
+
+ public static final int INITIAL = 0;
+ public static final int FAILED = 1;
+ public static final int PROCESSING = 2;
+
+ public interface AutoLoginCallback {
+ public void loginFailed();
+ }
+
+ public DeviceAccountLogin(Activity activity, WebView view, Tab tab,
+ WebViewController controller) {
+ mActivity = activity;
+ mWebView = view;
+ mTab = tab;
+ mWebViewController = controller;
+ mAccountManager = AccountManager.get(activity);
+ }
+
+ public void handleLogin(String realm, String account, String args) {
+ mAccounts = mAccountManager.getAccountsByType(realm);
+ mAuthToken = "weblogin:" + args;
+
+ // No need to display UI if there are no accounts.
+ if (mAccounts.length == 0) {
+ return;
+ }
+
+ // Verify the account before using it.
+ for (Account a : mAccounts) {
+ if (a.name.equals(account)) {
+ // Handle the automatic login case where the service gave us an
+ // account to use.
+ mAccountManager.getAuthToken(a, mAuthToken, null,
+ mActivity, this, null);
+ return;
+ }
+ }
+
+ displayLoginUi();
+ }
+
+ @Override
+ public void run(AccountManagerFuture<Bundle> value) {
+ try {
+ String result = value.getResult().getString(
+ AccountManager.KEY_AUTHTOKEN);
+ if (result == null) {
+ loginFailed();
+ } else {
+ mWebView.loadUrl(result);
+ mTab.setDeviceAccountLogin(null);
+ if (mTab.inForeground()) {
+ mWebViewController.hideAutoLogin(mTab);
+ }
+ }
+ } catch (Exception e) {
+ loginFailed();
+ }
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ private void loginFailed() {
+ mState = FAILED;
+ if (mTab.getDeviceAccountLogin() == null) {
+ displayLoginUi();
+ } else {
+ if (mCallback != null) {
+ mCallback.loginFailed();
+ }
+ }
+ }
+
+ private void displayLoginUi() {
+ // Display the account picker.
+ mTab.setDeviceAccountLogin(this);
+ if (mTab.inForeground()) {
+ mWebViewController.showAutoLogin(mTab);
+ }
+ }
+
+ public void cancel() {
+ mTab.setDeviceAccountLogin(null);
+ }
+
+ public void login(int accountIndex, AutoLoginCallback cb) {
+ mState = PROCESSING;
+ mCallback = cb;
+ mAccountManager.getAuthToken(
+ mAccounts[accountIndex], mAuthToken, null,
+ mActivity, this, null);
+ }
+
+ public String[] getAccountNames() {
+ String[] names = new String[mAccounts.length];
+ for (int i = 0; i < mAccounts.length; i++) {
+ names[i] = mAccounts[i].name;
+ }
+ return names;
+ }
+}
diff --git a/src/com/android/browser/GoogleAccountLogin.java b/src/com/android/browser/GoogleAccountLogin.java
index f4ccfd1..0bde010 100644
--- a/src/com/android/browser/GoogleAccountLogin.java
+++ b/src/com/android/browser/GoogleAccountLogin.java
@@ -40,7 +40,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
-import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -80,10 +79,10 @@ public class GoogleAccountLogin implements Runnable,
private int mState; // {NONE(0), SID(1), LSID(2)}
private boolean mTokensInvalidated;
- private GoogleAccountLogin(Activity activity, String name,
+ private GoogleAccountLogin(Activity activity, Account account,
Runnable runnable) {
mActivity = activity;
- mAccount = new Account(name, GOOGLE);
+ mAccount = account;
mWebView = new WebView(mActivity);
mRunnable = runnable;
@@ -233,28 +232,22 @@ public class GoogleAccountLogin implements Runnable,
// Start the login process if auto-login is enabled and the user is not
// already logged in.
public static void startLoginIfNeeded(Activity activity,
- BrowserSettings settings, Runnable runnable) {
- // Auto login not enabled?
- if (!settings.isAutoLoginEnabled()) {
+ Runnable runnable) {
+ // Already logged in?
+ if (isLoggedIn(activity)) {
runnable.run();
return;
}
// No account found?
- String account = settings.getAutoLoginAccount(activity);
- if (account == null) {
- runnable.run();
- return;
- }
-
- // Already logged in?
- if (isLoggedIn(activity)) {
+ Account[] accounts = getAccounts(activity);
+ if (accounts == null || accounts.length == 0) {
runnable.run();
return;
}
GoogleAccountLogin login =
- new GoogleAccountLogin(activity, account, runnable);
+ new GoogleAccountLogin(activity, accounts[0], runnable);
login.startLogin();
}
@@ -271,31 +264,12 @@ public class GoogleAccountLogin implements Runnable,
mAccount, "SID", null, mActivity, this, null);
}
- // Returns the account name passed in if the account exists, otherwise
- // returns the default account.
- public static String validateAccount(Context ctx, String name) {
- Account[] accounts = getAccounts(ctx);
- if (accounts.length == 0) {
- return null;
- }
- if (name != null) {
- // Make sure the account still exists.
- for (Account a : accounts) {
- if (a.name.equals(name)) {
- return name;
- }
- }
- }
- // Return the first entry.
- return accounts[0].name;
- }
-
- public static Account[] getAccounts(Context ctx) {
+ private static Account[] getAccounts(Context ctx) {
return AccountManager.get(ctx).getAccountsByType(GOOGLE);
}
- // Checks for the presence of the SID cookie on google.com.
- public static boolean isLoggedIn(Context ctx) {
+ // Checks if we already did pre-login.
+ private static boolean isLoggedIn(Context ctx) {
// See if we last logged in less than a week ago.
long lastLogin = PreferenceManager.
getDefaultSharedPreferences(ctx).
@@ -303,31 +277,7 @@ public class GoogleAccountLogin implements Runnable,
if (lastLogin == -1) {
return false;
}
- long diff = System.currentTimeMillis() - lastLogin;
- if (diff > WEEK_IN_MILLIS) {
- Log.d(LOGTAG, "Forcing login after " + diff + "ms");
- return false;
- }
-
- // This will potentially block the UI thread but we have to have the
- // most updated cookies.
- // FIXME: Figure out how to avoid waiting to clear session cookies.
- CookieManager.getInstance().waitForCookieOperationsToComplete();
-
- // Use /a/ to grab hosted cookies as well as the base set of google.com
- // cookies.
- String cookies = CookieManager.getInstance().getCookie(
- "http://www.google.com/a/");
- if (cookies != null) {
- StringTokenizer tokenizer = new StringTokenizer(cookies, ";");
- while (tokenizer.hasMoreTokens()) {
- String cookie = tokenizer.nextToken().trim();
- if (cookie.startsWith("SID=") || cookie.startsWith("ASIDAP=")) {
- return true;
- }
- }
- }
- return false;
+ return true;
}
// Used to indicate that the Browser should continue loading the main page.
diff --git a/src/com/android/browser/InstantSearchEngine.java b/src/com/android/browser/InstantSearchEngine.java
index 128997c..85e494a 100644
--- a/src/com/android/browser/InstantSearchEngine.java
+++ b/src/com/android/browser/InstantSearchEngine.java
@@ -15,13 +15,9 @@
*/
package com.android.browser;
-import com.google.android.collect.Maps;
-import com.google.common.collect.Lists;
-
import com.android.browser.Controller;
import com.android.browser.R;
import com.android.browser.UI.DropdownChangeListener;
-import com.android.browser.search.DefaultSearchEngine;
import com.android.browser.search.SearchEngine;
import android.app.SearchManager;
@@ -81,7 +77,7 @@ public class InstantSearchEngine implements SearchEngine, DropdownChangeListener
// If for some reason we are in a bad state, ensure that the
// user gets default search results at the very least.
- if (mSearchBox == null & !isInstantPage()) {
+ if (mSearchBox == null || !isInstantPage()) {
mWrapped.startSearch(context, query, appData, extraData);
return;
}
@@ -238,6 +234,10 @@ public class InstantSearchEngine implements SearchEngine, DropdownChangeListener
mController.registerDropdownChangeListener(this);
+ if (mSearchBox == null) {
+ return mWrapped.getSuggestions(context, query);
+ }
+
mSearchBox.setDimensions(0, 0, 0, mHeight);
mSearchBox.onresize();
diff --git a/src/com/android/browser/IntentHandler.java b/src/com/android/browser/IntentHandler.java
index 77db538..fa8bfbc 100644
--- a/src/com/android/browser/IntentHandler.java
+++ b/src/com/android/browser/IntentHandler.java
@@ -145,14 +145,18 @@ public class IntentHandler {
|| (activateVoiceSearch && appId != null))
&& !mActivity.getPackageName().equals(appId)
&& (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
- Tab appTab = mTabControl.getTabFromId(appId);
- if (appTab != null) {
- mController.reuseTab(appTab, appId, urlData);
- return;
+ if (activateVoiceSearch) {
+ Tab appTab = mTabControl.getTabFromId(appId);
+ if (appTab != null) {
+ mController.reuseTab(appTab, appId, urlData);
+ return;
+ } else {
+ mController.openTabAndShow(null, urlData, false, appId);
+ }
} else {
// No matching application tab, try to find a regular tab
// with a matching url.
- appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
+ Tab appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
if (appTab != null) {
if (current != appTab) {
mController.switchToTab(mTabControl.getTabIndex(appTab));
@@ -164,7 +168,7 @@ public class IntentHandler {
// MAX_TABS. Then the url will be opened in the current
// tab. If a new tab is created, it will have "true" for
// exit on close.
- mController.openTabAndShow(null, urlData, true, appId);
+ mController.openTabAndShow(null, urlData, false, appId);
}
}
} else {
diff --git a/src/com/android/browser/PieControl.java b/src/com/android/browser/PieControl.java
index 2e2eba4..38ed1bb 100644
--- a/src/com/android/browser/PieControl.java
+++ b/src/com/android/browser/PieControl.java
@@ -16,18 +16,32 @@
package com.android.browser;
+import com.android.browser.view.PieItem;
+import com.android.browser.view.PieListView;
import com.android.browser.view.PieMenu;
+import com.android.browser.view.PieMenu.PieView.OnLayoutListener;
+import com.android.browser.view.PieStackView;
+import com.android.browser.view.PieStackView.OnCurrentListener;
import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.webkit.WebView;
+import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.TextView;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
/**
* controller for Quick Controls pie menu
@@ -38,24 +52,25 @@ public class PieControl implements OnClickListener, PieMenu.PieController {
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;
+ private PieItem mBack;
+ private PieItem mForward;
+ private PieItem mRefresh;
+ private PieItem mUrl;
+ private PieItem mOptions;
+ private PieItem mBookmarks;
+ private PieItem mNewTab;
+ private PieItem mClose;
+ private MenuAdapter mMenuAdapter;
+ private PieItem mShowTabs;
+ private TabAdapter mTabAdapter;
+ private TextView mTabsCount;
+ private int mItemSize;
public PieControl(Activity activity, UiController controller, XLargeUi ui) {
mActivity = activity;
mUiController = controller;
mUi = ui;
- mTabItems = new HashMap<View, Tab>();
+ mItemSize = (int) activity.getResources().getDimension(R.dimen.qc_item_size);
}
protected void attachToContainer(FrameLayout container) {
@@ -64,42 +79,107 @@ public class PieControl implements OnClickListener, PieMenu.PieController {
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
mPie.setLayoutParams(lp);
- mNewTab = makeMenuView(R.drawable.ic_pie_new_tab);
- mPie.addItem(mNewTab);
- mBack = makeMenuView(R.drawable.ic_pie_back);
- mPie.addItem(mBack);
- mUrl = makeMenuView(R.drawable.ic_pie_web);
- mPie.addItem(mUrl);
- mBookmarks = makeMenuView(R.drawable.ic_pie_bookmarks);
- mPie.addItem(mBookmarks);
- mOptions = makeMenuView(R.drawable.ic_pie_more);
- mPie.addItem(mOptions);
+ mBack = makeItem(R.drawable.ic_back_holo_dark, 1);
+ mUrl = makeItem(R.drawable.ic_web_holo_dark, 1);
+ mBookmarks = makeItem(R.drawable.ic_bookmarks_holo_dark, 1);
+ mRefresh = makeItem(R.drawable.ic_refresh_holo_dark, 2);
+ mForward = makeItem(R.drawable.ic_forward_holo_dark, 2);
+ mNewTab = makeItem(R.drawable.ic_new_window_holo_dark, 2);
+ mClose = makeItem(R.drawable.ic_close_window_holo_dark, 2);
+ View tabs = makeTabsView();
+ mShowTabs = new PieItem(tabs, 2);
+ mOptions = makeItem(
+ com.android.internal.R.drawable.ic_menu_moreoverflow_normal_holo_dark,
+ 2);
+ mMenuAdapter = new MenuAdapter(mActivity, mUiController);
+ mTabAdapter = new TabAdapter(mActivity, mUiController);
+ PieStackView stack = new PieStackView(mActivity);
+ stack.setLayoutListener(new OnLayoutListener() {
+ @Override
+ public void onLayout(int ax, int ay, boolean left) {
+ buildTabs();
+ }
+ });
+ stack.setOnCurrentListener(mTabAdapter);
+ stack.setAdapter(mTabAdapter);
+ mShowTabs.setPieView(stack);
+ PieListView menuview = new PieListView(mActivity);
+ menuview.setLayoutListener(new OnLayoutListener() {
+ @Override
+ public void onLayout(int ax, int ay, boolean left) {
+ mActivity.openOptionsMenu();
+ }
+ });
+
+ mOptions.setPieView(menuview);
+ menuview.setAdapter(mMenuAdapter);
setClickListener(mBack,
+ mRefresh,
+ mForward,
mUrl,
- mOptions,
mBookmarks,
- mNewTab
+ mNewTab,
+ mClose
);
+ // level 1
+ mPie.addItem(mBack);
+ mPie.addItem(mUrl);
+ mPie.addItem(mBookmarks);
+ // level 2
+ mPie.addItem(mForward);
+ mPie.addItem(mRefresh);
+ mPie.addItem(mShowTabs);
+ mPie.addItem(mNewTab);
+ mPie.addItem(mClose);
+ mPie.addItem(mOptions);
mPie.setController(this);
}
container.addView(mPie);
}
+ private void buildTabs() {
+ final List<Tab> tabs = mUiController.getTabs();
+ mUi.captureTab(mUi.getActiveTab());
+ mTabAdapter.setTabs(tabs);
+ PieStackView sym = (PieStackView) mShowTabs.getPieView();
+ sym.setCurrent(mUiController.getTabControl().getCurrentIndex());
+
+ }
+
+ protected void onMenuOpened(Menu menu) {
+ mMenuAdapter.setMenu(menu);
+ }
+
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 View makeTabsView() {
+ View v = mActivity.getLayoutInflater().inflate(R.layout.qc_tabs_view, null);
+ mTabsCount = (TextView) v.findViewById(R.id.label);
+ mTabsCount.setText("1");
+ ImageView image = (ImageView) v.findViewById(R.id.icon);
+ image.setImageResource(R.drawable.ic_windows_holo_dark);
+ image.setScaleType(ScaleType.CENTER);
+ LayoutParams lp = new LayoutParams(mItemSize, mItemSize);
+ v.setLayoutParams(lp);
+ return v;
+ }
+
+ private PieItem makeItem(int image, int l) {
+ ImageView view = new ImageView(mActivity);
+ view.setImageResource(image);
+ view.setMinimumWidth(mItemSize);
+ view.setMinimumHeight(mItemSize);
+ view.setScaleType(ScaleType.CENTER);
+ LayoutParams lp = new LayoutParams(mItemSize, mItemSize);
+ view.setLayoutParams(lp);
+ return new PieItem(view, l);
}
- private void setClickListener(View... views) {
- for (View view : views) {
- view.setOnClickListener(this);
+ private void setClickListener(PieItem... items) {
+ for (PieItem item : items) {
+ item.getView().setOnClickListener(this);
}
}
@@ -112,41 +192,167 @@ public class PieControl implements OnClickListener, PieMenu.PieController {
@Override
public void onClick(View v) {
- mPie.show(false);
Tab tab = mUiController.getTabControl().getCurrentTab();
WebView web = tab.getWebView();
- if (mBack == v) {
+ if (mBack.getView() == v) {
web.goBack();
- } else if (mForward == v) {
+ } else if (mForward.getView() == v) {
web.goForward();
- } else if (mRefresh == v) {
+ } else if (mRefresh.getView() == v) {
if (tab.inPageLoad()) {
web.stopLoading();
} else {
web.reload();
}
- } else if (mUrl == v) {
+ } else if (mUrl.getView() == v) {
mUi.showTitleBarAndEdit();
- } else if (mOptions == v) {
- mActivity.openOptionsMenu();
- } else if (mBookmarks == v) {
+ } else if (mBookmarks.getView() == v) {
mUiController.bookmarksOrHistoryPicker(false);
- } else if (mNewTab == v) {
+ } else if (mNewTab.getView() == v) {
mUiController.openTabToHomePage();
mUi.showTitleBarAndEdit();
- } else if (mClose == v) {
+ } else if (mClose.getView() == v) {
mUiController.closeCurrentTab();
- } else {
- Tab ntab = mTabItems.get(v);
- if (ntab != null) {
- mUiController.switchToTab(mUiController.getTabControl().getTabIndex(ntab));
- }
}
}
@Override
public boolean onOpen() {
+ int n = mUiController.getTabControl().getTabCount();
+ mTabsCount.setText(Integer.toString(n));
return true;
}
+ private static class TabAdapter extends BaseAdapter implements OnCurrentListener {
+
+ LayoutInflater mInflater;
+ UiController mUiController;
+ private List<Tab> mTabs;
+ private int mCurrent;
+
+ public TabAdapter(Context ctx, UiController ctl) {
+ mInflater = LayoutInflater.from(ctx);
+ mUiController = ctl;
+ mTabs = new ArrayList<Tab>();
+ mCurrent = -1;
+ }
+
+ public void setTabs(List<Tab> tabs) {
+ mTabs = tabs;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return mTabs.size();
+ }
+
+ @Override
+ public Tab getItem(int position) {
+ return mTabs.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final Tab tab = mTabs.get(position);
+ View view = mInflater.inflate(R.layout.qc_tab,
+ null);
+ ImageView thumb = (ImageView) view.findViewById(R.id.thumb);
+ TextView title1 = (TextView) view.findViewById(R.id.title1);
+ TextView title2 = (TextView) view.findViewById(R.id.title2);
+ Bitmap b = tab.getScreenshot();
+ if (b != null) {
+ thumb.setImageBitmap(b);
+ }
+ if (position > mCurrent) {
+ title1.setVisibility(View.GONE);
+ title2.setText(tab.getTitle());
+ } else {
+ title2.setVisibility(View.GONE);
+ title1.setText(tab.getTitle());
+ }
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mUiController.switchToTab(mUiController.getTabControl()
+ .getTabIndex(tab));
+ }
+ });
+ return view;
+ }
+
+ @Override
+ public void onSetCurrent(int index) {
+ mCurrent = index;
+ }
+
+ }
+
+ private static class MenuAdapter extends BaseAdapter
+ implements OnClickListener {
+
+ List<MenuItem> mItems;
+ UiController mUiController;
+ LayoutInflater mInflater;
+
+ public MenuAdapter(Context ctx, UiController ctl) {
+ mUiController = ctl;
+ mInflater = LayoutInflater.from(ctx);
+ mItems = new ArrayList<MenuItem>();
+ }
+
+ public void setMenu(Menu menu) {
+ mItems.clear();
+ for (int i = 0; i < menu.size(); i++) {
+ MenuItem item = menu.getItem(i);
+ if (item.isEnabled() && item.isVisible()) {
+ mItems.add(item);
+ }
+ }
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public MenuItem getItem(int position) {
+ return mItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getTag() != null) {
+ mUiController.onOptionsItemSelected((MenuItem) v.getTag());
+ }
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final MenuItem item = mItems.get(position);
+ View view = mInflater.inflate(
+ R.layout.qc_menu_item, null);
+ TextView label =
+ (TextView) view.findViewById(R.id.title);
+ label.setText(item.getTitle());
+ label.setTag(item);
+ label.setOnClickListener(this);
+ label.setLayoutParams(new LayoutParams(240, 32));
+ return label;
+ }
+
+ }
+
}
diff --git a/src/com/android/browser/ScrollWebView.java b/src/com/android/browser/ScrollWebView.java
index 2ee2ac0..8c89e51 100644
--- a/src/com/android/browser/ScrollWebView.java
+++ b/src/com/android/browser/ScrollWebView.java
@@ -18,7 +18,6 @@ package com.android.browser;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -36,9 +35,7 @@ public class ScrollWebView extends WebView implements Runnable {
private boolean mBackgroundRemoved = false;
private boolean mUserInitiated = false;
private TitleBarBase mTitleBar;
- private boolean mDrawCached = false;
private Bitmap mBitmap;
- private Paint mCachePaint = new Paint();
/**
* @param context
@@ -137,46 +134,22 @@ public class ScrollWebView extends WebView implements Runnable {
mScrollListener = l;
}
- @Override
- public void invalidate() {
- if (!mDrawCached) {
- super.invalidate();
- }
- }
-
// callback for scroll events
interface ScrollListener {
public void onScroll(int visibleTitleHeight, boolean userInitiated);
}
- void setDrawCached(boolean cached) {
- if (cached == mDrawCached) return;
- if (cached) {
- buildDrawingCache();
- mBitmap = getDrawingCache(false);
- mDrawCached = (mBitmap != null);
- } else {
- mDrawCached = false;
- mBitmap = null;
- destroyDrawingCache();
- }
- }
-
@Override
protected void onDraw(android.graphics.Canvas c) {
- if (mDrawCached) {
- c.drawBitmap(mBitmap, getScrollX(), getScrollY(), mCachePaint);
- } else {
- super.onDraw(c);
- if (!mBackgroundRemoved && getRootView().getBackground() != null) {
- mBackgroundRemoved = true;
- post(new Runnable() {
- public void run() {
- getRootView().setBackgroundDrawable(null);
- }
- });
- }
+ super.onDraw(c);
+ if (!mBackgroundRemoved && getRootView().getBackground() != null) {
+ mBackgroundRemoved = true;
+ post(new Runnable() {
+ public void run() {
+ getRootView().setBackgroundDrawable(null);
+ }
+ });
}
}
diff --git a/src/com/android/browser/SuggestionsAdapter.java b/src/com/android/browser/SuggestionsAdapter.java
index 6a9111f..ecdaa15 100644
--- a/src/com/android/browser/SuggestionsAdapter.java
+++ b/src/com/android/browser/SuggestionsAdapter.java
@@ -70,7 +70,6 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable,
final int mLinesLandscape;
final Object mResultsLock = new Object();
List<String> mVoiceResults;
- boolean mReverseResults;
boolean mIncognitoMode;
interface CompletionListener {
@@ -137,9 +136,6 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable,
@Override
public SuggestItem getItem(int position) {
- if (mReverseResults) {
- position = (getCount() - 1) - position;
- }
if (mVoiceResults != null) {
SuggestItem item = new SuggestItem(mVoiceResults.get(position),
null, TYPE_VOICE_SEARCH);
@@ -152,10 +148,6 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable,
return mMixedResults.items.get(position);
}
- public void setReverseResults(boolean reverse) {
- mReverseResults = reverse;
- }
-
@Override
public long getItemId(int position) {
return position;
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 47cefbc..863fc95 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -132,10 +132,14 @@ class Tab {
// Listener used to know when we move forward or back in the history list.
private final WebBackForwardListClient mWebBackForwardListClient;
private DataController mDataController;
+ // State of the auto-login request.
+ private DeviceAccountLogin mDeviceAccountLogin;
// AsyncTask for downloading touch icons
DownloadTouchIcon mTouchIconLoader;
+ private Bitmap mScreenshot;
+
// All the state needed for a page
private static class PageState {
String mUrl;
@@ -191,6 +195,7 @@ class Tab {
static final String APPID = "appid";
static final String ORIGINALURL = "originalUrl";
static final String INCOGNITO = "privateBrowsingEnabled";
+ static final String SCREENSHOT = "screenshot";
// -------------------------------------------------------------------------
@@ -527,6 +532,13 @@ class Tab {
}
}
+ // Cancel the auto-login process.
+ if (mDeviceAccountLogin != null) {
+ mDeviceAccountLogin.cancel();
+ mDeviceAccountLogin = null;
+ mWebViewController.hideAutoLogin(Tab.this);
+ }
+
// finally update the UI in the activity if it is in the foreground
mWebViewController.onPageStarted(Tab.this, view, favicon);
@@ -809,8 +821,27 @@ class Tab {
}
mWebViewController.onUnhandledKeyEvent(event);
}
+
+ @Override
+ public void onReceivedLoginRequest(WebView view, String realm,
+ String account, String args) {
+ new DeviceAccountLogin(mActivity, view, Tab.this, mWebViewController)
+ .handleLogin(realm, account, args);
+ }
+
};
+ // Called by DeviceAccountLogin when the Tab needs to have the auto-login UI
+ // displayed.
+ void setDeviceAccountLogin(DeviceAccountLogin login) {
+ mDeviceAccountLogin = login;
+ }
+
+ // Returns non-null if the title bar should display the auto-login UI.
+ DeviceAccountLogin getDeviceAccountLogin() {
+ return mDeviceAccountLogin;
+ }
+
// -------------------------------------------------------------------------
// WebChromeClient implementation for the main WebView
// -------------------------------------------------------------------------
@@ -1720,6 +1751,9 @@ class Tab {
mSavedState.putInt(PARENTTAB, mWebViewController.getTabControl().getTabIndex(
mParentTab));
}
+ if (mScreenshot != null) {
+ mSavedState.putParcelable(SCREENSHOT, mScreenshot);
+ }
return true;
}
@@ -1735,6 +1769,7 @@ class Tab {
mSavedState = null;
mCloseOnExit = b.getBoolean(CLOSEONEXIT);
mAppId = b.getString(APPID);
+ mScreenshot = b.getParcelable(SCREENSHOT);
final WebBackForwardList list = mMainView.restoreState(b);
if (list == null) {
@@ -1757,4 +1792,13 @@ class Tab {
}
}
};
+
+ public void setScreenshot(Bitmap screenshot) {
+ mScreenshot = screenshot;
+ }
+
+ public Bitmap getScreenshot() {
+ return mScreenshot;
+ }
+
}
diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java
index 8a715b1..5f02002 100644
--- a/src/com/android/browser/TitleBarXLarge.java
+++ b/src/com/android/browser/TitleBarXLarge.java
@@ -16,38 +16,46 @@
package com.android.browser;
-import com.android.browser.autocomplete.SuggestedTextController.TextChangeWatcher;
import com.android.browser.UI.DropdownChangeListener;
+import com.android.browser.autocomplete.SuggestedTextController.TextChangeWatcher;
import com.android.browser.search.SearchEngine;
+import android.accounts.Account;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
-import android.database.DataSetObserver;
import android.graphics.Bitmap;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
+import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
import android.view.LayoutInflater;
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.webkit.WebView;
import android.widget.AbsoluteLayout;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.Spinner;
+import android.widget.TextView;
-import java.util.ArrayList;
import java.util.List;
/**
* tabbed title bar for xlarge screen browser
*/
public class TitleBarXLarge extends TitleBarBase
- implements OnClickListener, OnFocusChangeListener, TextChangeWatcher {
+ implements OnClickListener, OnFocusChangeListener, TextChangeWatcher,
+ DeviceAccountLogin.AutoLoginCallback {
private XLargeUi mUi;
@@ -69,6 +77,15 @@ public class TitleBarXLarge extends TitleBarBase
private PageProgressView mProgressView;
private Drawable mFocusDrawable;
private Drawable mUnfocusDrawable;
+ // Auto-login UI
+ private View mAutoLogin;
+ private Spinner mAutoLoginAccount;
+ private Button mAutoLoginLogin;
+ private ProgressBar mAutoLoginProgress;
+ private TextView mAutoLoginError;
+ private ImageButton mAutoLoginCancel;
+ private DeviceAccountLogin mAutoLoginHandler;
+ private ArrayAdapter<String> mAccountsAdapter;
private boolean mInLoad;
private boolean mUseQuickControls;
@@ -84,8 +101,8 @@ public class TitleBarXLarge extends TitleBarBase
R.drawable.textfield_active_holo_dark);
mUnfocusDrawable = resources.getDrawable(
R.drawable.textfield_default_holo_dark);
- initLayout(activity);
mInVoiceMode = false;
+ initLayout(activity);
}
@Override
@@ -136,6 +153,17 @@ public class TitleBarXLarge extends TitleBarBase
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 =
+ (ImageButton) mAutoLogin.findViewById(R.id.autologin_close);
+ mAutoLoginCancel.setOnClickListener(this);
+
setFocusState(false);
}
@@ -151,6 +179,67 @@ public class TitleBarXLarge extends TitleBarBase
}
}
+ void updateAutoLogin(Tab tab, boolean animate) {
+ DeviceAccountLogin login = tab.getDeviceAccountLogin();
+ if (login != null) {
+ mAutoLoginHandler = login;
+ mAutoLogin.setVisibility(View.VISIBLE);
+ 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.GONE);
+ 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.GONE);
+ mAutoLoginError.setVisibility(View.VISIBLE);
+ break;
+ case DeviceAccountLogin.INITIAL:
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ if (mUseQuickControls) {
+ mUi.showTitleBar();
+ } else {
+ if (animate) {
+ mAutoLogin.startAnimation(AnimationUtils.loadAnimation(
+ getContext(), R.anim.autologin_enter));
+ }
+ }
+ } else {
+ mAutoLoginHandler = null;
+ if (mUseQuickControls) {
+ mUi.hideTitleBar();
+ mAutoLogin.setVisibility(View.GONE);
+ mUi.refreshWebView();
+ } else {
+ if (animate) {
+ hideAutoLogin();
+ } else if (mAutoLogin.getAnimation() == null) {
+ mAutoLogin.setVisibility(View.GONE);
+ mUi.refreshWebView();
+ }
+ }
+ }
+ }
+
+ boolean inAutoLogin() {
+ return mAutoLoginHandler != null;
+ }
+
private ViewGroup.LayoutParams makeLayoutParams() {
if (mUseQuickControls) {
return new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
@@ -164,17 +253,20 @@ public class TitleBarXLarge extends TitleBarBase
@Override
public int getEmbeddedHeight() {
- return mContainer.getHeight();
+ int height = mContainer.getHeight();
+ if (mAutoLogin.getVisibility() == View.VISIBLE) {
+ height += mAutoLogin.getHeight();
+ }
+ return height;
}
void setUseQuickControls(boolean useQuickControls) {
mUseQuickControls = useQuickControls;
- mUrlInput.setUseQuickControls(mUseQuickControls);
setLayoutParams(makeLayoutParams());
}
void setShowProgressOnly(boolean progress) {
- if (progress) {
+ if (progress && !inAutoLogin()) {
mContainer.setVisibility(View.GONE);
} else {
mContainer.setVisibility(View.VISIBLE);
@@ -200,6 +292,13 @@ public class TitleBarXLarge extends TitleBarBase
if (mUseQuickControls) {
mUi.hideTitleBar();
}
+
+ if (mUrlInput.getText().length() == 0) {
+ Tab currentTab = mUiController.getTabControl().getCurrentTab();
+ if (currentTab != null) {
+ mUrlInput.setText(currentTab.getUrl(), false);
+ }
+ }
}
mUrlInput.clearNeedsUpdate();
}
@@ -237,6 +336,20 @@ public class TitleBarXLarge extends TitleBarBase
mUrlInput.clearFocus();
}
+ private void hideAutoLogin() {
+ Animation anim = AnimationUtils.loadAnimation(
+ getContext(), R.anim.autologin_exit);
+ anim.setAnimationListener(new AnimationListener() {
+ @Override public void onAnimationEnd(Animation a) {
+ mAutoLogin.setVisibility(View.GONE);
+ mUi.refreshWebView();
+ }
+ @Override public void onAnimationStart(Animation a) {}
+ @Override public void onAnimationRepeat(Animation a) {}
+ });
+ mAutoLogin.startAnimation(anim);
+ }
+
@Override
public void onClick(View v) {
if (mBackButton == v) {
@@ -261,10 +374,33 @@ public class TitleBarXLarge extends TitleBarBase
clearOrClose();
} else if (mVoiceSearch == v) {
mUiController.startVoiceSearch();
+ } else if (mAutoLoginCancel == v) {
+ if (mAutoLoginHandler != null) {
+ mAutoLoginHandler.cancel();
+ mAutoLoginHandler = null;
+ }
+ hideAutoLogin();
+ } 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);
+ }
}
}
@Override
+ public void loginFailed() {
+ mAutoLoginAccount.setEnabled(true);
+ mAutoLoginLogin.setEnabled(true);
+ mAutoLoginProgress.setVisibility(View.GONE);
+ mAutoLoginError.setVisibility(View.VISIBLE);
+ }
+
+ @Override
void setFavicon(Bitmap icon) { }
private void clearOrClose() {
@@ -380,7 +516,9 @@ public class TitleBarXLarge extends TitleBarBase
public void setInVoiceMode(boolean voicemode, List<String> voiceResults) {
mInVoiceMode = voicemode;
mUrlInput.setVoiceResults(voiceResults);
- mUrlIcon.setImageDrawable(mSearchButton.getDrawable());
+ if (voicemode) {
+ mUrlIcon.setImageDrawable(mSearchButton.getDrawable());
+ }
}
@Override
@@ -396,6 +534,10 @@ public class TitleBarXLarge extends TitleBarBase
return super.focusSearch(focused, dir);
}
+ void clearCompletions() {
+ mUrlInput.setSuggestedText(null);
+ }
+
@Override
public boolean dispatchKeyEventPreIme(KeyEvent evt) {
if (evt.getKeyCode() == KeyEvent.KEYCODE_BACK) {
diff --git a/src/com/android/browser/UI.java b/src/com/android/browser/UI.java
index bec7034..368c829 100644
--- a/src/com/android/browser/UI.java
+++ b/src/com/android/browser/UI.java
@@ -89,6 +89,8 @@ public interface UI {
public void revertVoiceTitleBar(Tab tab);
+ public boolean onPrepareOptionsMenu(Menu menu);
+
public void onOptionsMenuOpened();
public void onExtendedMenuOpened();
@@ -122,9 +124,12 @@ public interface UI {
boolean dispatchKey(int code, KeyEvent event);
-
public static interface DropdownChangeListener {
void onNewDropdownDimensions(int height);
}
void registerDropdownChangeListener(DropdownChangeListener d);
+
+ void showAutoLogin(Tab tab);
+
+ void hideAutoLogin(Tab tab);
}
diff --git a/src/com/android/browser/UiController.java b/src/com/android/browser/UiController.java
index 551d0ce..65fa5f8 100644
--- a/src/com/android/browser/UiController.java
+++ b/src/com/android/browser/UiController.java
@@ -19,6 +19,7 @@ package com.android.browser;
import com.android.browser.UI.DropdownChangeListener;
import android.content.Intent;
+import android.view.MenuItem;
import android.webkit.WebView;
import java.util.List;
@@ -88,4 +89,7 @@ public interface UiController extends BookmarksHistoryCallbacks {
void unregisterOptionsMenuHandler(OptionsMenuHandler handler);
void registerDropdownChangeListener(DropdownChangeListener d);
+
+ boolean onOptionsItemSelected(MenuItem item);
+
}
diff --git a/src/com/android/browser/UrlInputView.java b/src/com/android/browser/UrlInputView.java
index b7f2bff..350d772 100644
--- a/src/com/android/browser/UrlInputView.java
+++ b/src/com/android/browser/UrlInputView.java
@@ -127,10 +127,6 @@ public class UrlInputView extends SuggestiveAutoCompleteTextView
setCustomSelectionActionModeCallback(urlSelectionMode);
}
- void setUseQuickControls(boolean useQuickControls) {
- mAdapter.setReverseResults(useQuickControls);
- }
-
void setContainer(View container) {
mContainer = container;
}
diff --git a/src/com/android/browser/WebViewController.java b/src/com/android/browser/WebViewController.java
index 813b63b..6b44207 100644
--- a/src/com/android/browser/WebViewController.java
+++ b/src/com/android/browser/WebViewController.java
@@ -112,4 +112,7 @@ public interface WebViewController {
void bookmarkedStatusHasChanged(Tab tab);
+ void showAutoLogin(Tab tab);
+
+ void hideAutoLogin(Tab tab);
}
diff --git a/src/com/android/browser/XLargeUi.java b/src/com/android/browser/XLargeUi.java
index 371e649..13018af 100644
--- a/src/com/android/browser/XLargeUi.java
+++ b/src/com/android/browser/XLargeUi.java
@@ -17,7 +17,6 @@
package com.android.browser;
import com.android.browser.ScrollWebView.ScrollListener;
-import com.android.browser.UI.DropdownChangeListener;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
@@ -25,11 +24,14 @@ import android.animation.ObjectAnimator;
import android.app.ActionBar;
import android.app.Activity;
import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
import android.os.Bundle;
+import android.os.Handler;
import android.util.Log;
import android.view.ActionMode;
import android.view.Gravity;
import android.view.KeyEvent;
+import android.view.Menu;
import android.view.View;
import android.webkit.WebChromeClient.CustomViewCallback;
import android.webkit.WebView;
@@ -48,10 +50,12 @@ public class XLargeUi extends BaseUi implements ScrollListener {
private TabBar mTabBar;
private TitleBarXLarge mTitleBar;
+ private Animator mTitleBarAnimator;
+ private boolean mSkipTitleBarAnimations;
private boolean mUseQuickControls;
private PieControl mPieControl;
- private boolean mInAnimation = false;
+ private Handler mHandler;
/**
* @param browser
@@ -59,6 +63,7 @@ public class XLargeUi extends BaseUi implements ScrollListener {
*/
public XLargeUi(Activity browser, UiController controller) {
super(browser, controller);
+ mHandler = new Handler();
mTitleBar = new TitleBarXLarge(mActivity, mUiController, this);
mTitleBar.setProgress(100);
mTabBar = new TabBar(mActivity, mUiController, this);
@@ -83,10 +88,12 @@ public class XLargeUi extends BaseUi implements ScrollListener {
@Override
public void hideComboView() {
- checkTabCount();
- super.hideComboView();
- // ComboView changes the action bar, set it back up to what we want
- setupActionBar();
+ if (isComboViewShowing()) {
+ super.hideComboView();
+ // ComboView changes the action bar, set it back up to what we want
+ setupActionBar();
+ checkTabCount();
+ }
}
private void setUseQuickControls(boolean useQuickControls) {
@@ -100,29 +107,35 @@ public class XLargeUi extends BaseUi implements ScrollListener {
if ((tab != null) && (tab.getWebView() != null)) {
tab.getWebView().setEmbeddedTitleBar(null);
}
- setTitleGravity(Gravity.BOTTOM);
} else {
mActivity.getActionBar().show();
if (mPieControl != null) {
mPieControl.removeFromContainer(mContentView);
}
- setTitleGravity(Gravity.TOP);
WebView web = mTabControl.getCurrentWebView();
if (web != null) {
web.setEmbeddedTitleBar(mTitleBar);
}
+ setTitleGravity(Gravity.NO_GRAVITY);
}
mTabBar.setUseQuickControls(mUseQuickControls);
}
private void checkTabCount() {
if (mUseQuickControls) {
- int n = mTabBar.getTabCount();
- if (n >= 2) {
- mActivity.getActionBar().show();
- } else if (n == 1) {
- mActivity.getActionBar().hide();
- }
+ mHandler.post(new Runnable() {
+ public void run() {
+ mActionBar.hide();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (!BrowserSettings.getInstance().useInstant()) {
+ mTitleBar.clearCompletions();
}
}
@@ -176,11 +189,10 @@ public class XLargeUi extends BaseUi implements ScrollListener {
if (tab.inForeground()) {
mTitleBar.setProgress(progress);
if (progress == 100) {
- if (!mTitleBar.isEditingUrl()) {
+ if (!mTitleBar.isEditingUrl() && !mTitleBar.inAutoLogin()) {
hideTitleBar();
if (mUseQuickControls) {
mTitleBar.setShowProgressOnly(false);
- setTitleGravity(Gravity.BOTTOM);
}
}
} else {
@@ -211,56 +223,16 @@ public class XLargeUi extends BaseUi implements ScrollListener {
@Override
public void setActiveTab(final Tab tab) {
- if (mInAnimation) return;
- if ((tab != mActiveTab) && (mActiveTab != null)) {
- mInAnimation = true;
- // animate between the two
- final ScrollWebView fromWV = (ScrollWebView) mActiveTab.getWebView();
- fromWV.setDrawCached(true);
- fromWV.setEmbeddedTitleBar(null);
- final ScrollWebView toWV = (ScrollWebView) tab.getWebView();
- if (!mUseQuickControls) {
- if (mTitleBar.getParent() == null) {
- toWV.setEmbeddedTitleBar(mTitleBar);
- }
+ cancelTitleBarAnimation(true);
+ mSkipTitleBarAnimations = true;
+ if (mUseQuickControls) {
+ if (mActiveTab != null) {
+ captureTab(mActiveTab);
}
- toWV.setDrawCached(true);
- attachTabToContentView(tab);
- super.setActiveTab(tab, false);
- ObjectAnimator transition = ObjectAnimator.ofFloat(
- toWV, "alpha", 0f, 1f);
- transition.setDuration(mActivity.getResources()
- .getInteger(R.integer.tabFadeDuration));
- transition.addListener(new AnimatorListener() {
- @Override
- public void onAnimationCancel(Animator animation) {
- fromWV.setDrawCached(false);
- toWV.setDrawCached(false);
- setActiveTab(tab, false);
- mInAnimation = false;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- fromWV.setDrawCached(false);
- toWV.setDrawCached(false);
- setActiveTab(tab, false);
- mInAnimation = false;
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- }
- });
- transition.start();
- } else {
- super.setActiveTab(tab, true);
- setActiveTab(tab, true);
}
+ super.setActiveTab(tab, true);
+ setActiveTab(tab, true);
+ mSkipTitleBarAnimations = false;
}
@Override
@@ -294,6 +266,15 @@ public class XLargeUi extends BaseUi implements ScrollListener {
tab.getTopWindow().requestFocus();
}
+ public void captureTab(final Tab tab) {
+ Bitmap sshot = Controller.createScreenshot(tab,
+ (int) mActivity.getResources()
+ .getDimension(R.dimen.qc_thumb_width),
+ (int) mActivity.getResources()
+ .getDimension(R.dimen.qc_thumb_height));
+ tab.setScreenshot(sshot);
+ }
+
@Override
public void updateTabs(List<Tab> tabs) {
mTabBar.updateTabs(tabs);
@@ -302,8 +283,11 @@ public class XLargeUi extends BaseUi implements ScrollListener {
@Override
public void removeTab(Tab tab) {
+ cancelTitleBarAnimation(true);
+ mSkipTitleBarAnimations = true;
super.removeTab(tab);
mTabBar.onRemoveTab(tab);
+ mSkipTitleBarAnimations = false;
}
protected void onRemoveTabCompleted(Tab tab) {
@@ -342,6 +326,18 @@ public class XLargeUi extends BaseUi implements ScrollListener {
if (mUseQuickControls) {
mContentView.addView(mTitleBar);
} else {
+ if (!mSkipTitleBarAnimations) {
+ cancelTitleBarAnimation(false);
+ int visibleHeight = getVisibleTitleHeight();
+ float startPos = (-mTitleBar.getEmbeddedHeight() + visibleHeight);
+ if (mTitleBar.getTranslationY() != 0) {
+ startPos = Math.max(startPos, mTitleBar.getTranslationY());
+ }
+ mTitleBarAnimator = ObjectAnimator.ofFloat(mTitleBar,
+ "translationY",
+ startPos, 0);
+ mTitleBarAnimator.start();
+ }
setTitleGravity(Gravity.TOP);
}
super.showTitleBar();
@@ -356,12 +352,63 @@ public class XLargeUi extends BaseUi implements ScrollListener {
if (mUseQuickControls) {
mContentView.removeView(mTitleBar);
} else {
- setTitleGravity(Gravity.NO_GRAVITY);
+ if (!mSkipTitleBarAnimations) {
+ cancelTitleBarAnimation(false);
+ int visibleHeight = getVisibleTitleHeight();
+ mTitleBarAnimator = ObjectAnimator.ofFloat(mTitleBar,
+ "translationY", mTitleBar.getTranslationY(),
+ (-mTitleBar.getEmbeddedHeight() + visibleHeight));
+ mTitleBarAnimator.addListener(mHideTileBarAnimatorListener);
+ mTitleBarAnimator.start();
+ } else {
+ setTitleGravity(Gravity.NO_GRAVITY);
+ }
}
super.hideTitleBar();
}
}
+ private void cancelTitleBarAnimation(boolean reset) {
+ if (mTitleBarAnimator != null) {
+ mTitleBarAnimator.cancel();
+ mTitleBarAnimator = null;
+ }
+ if (reset) {
+ mTitleBar.setTranslationY(0);
+ }
+ }
+
+ private int getVisibleTitleHeight() {
+ WebView webview = mActiveTab != null ? mActiveTab.getWebView() : null;
+ return webview != null ? webview.getVisibleTitleHeight() : 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) {
+ mTitleBar.setTranslationY(0);
+ }
+ setTitleGravity(Gravity.NO_GRAVITY);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mWasCanceled = true;
+ }
+ };
+
public boolean isEditingUrl() {
return mTitleBar.isEditingUrl();
}
@@ -399,9 +446,8 @@ public class XLargeUi extends BaseUi implements ScrollListener {
if (inLoad) {
// the titlebar was removed when the CAB was shown
// if the page is loading, show it again
- mTitleBar.setShowProgressOnly(true);
- if (!isTitleBarShowing()) {
- setTitleGravity(Gravity.TOP);
+ if (mUseQuickControls) {
+ mTitleBar.setShowProgressOnly(true);
}
showTitleBar();
}
@@ -413,6 +459,18 @@ public class XLargeUi extends BaseUi implements ScrollListener {
}
@Override
+ protected void updateAutoLogin(Tab tab, boolean animate) {
+ mTitleBar.updateAutoLogin(tab, animate);
+ }
+
+ protected void refreshWebView() {
+ Tab tab = getActiveTab();
+ if ((tab != null) && (tab.getWebView() != null)) {
+ tab.getWebView().invalidate();
+ }
+ }
+
+ @Override
public void setUrlTitle(Tab tab) {
super.setUrlTitle(tab);
mTabBar.onUrlAndTitle(tab, tab.getUrl(), tab.getTitle());
@@ -460,22 +518,23 @@ public class XLargeUi extends BaseUi implements ScrollListener {
@Override
public boolean dispatchKey(int code, KeyEvent event) {
- WebView web = getActiveTab().getWebView();
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
-
- switch (code) {
- case KeyEvent.KEYCODE_TAB:
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if ((web != null) && web.hasFocus() && !mTitleBar.hasFocus()) {
- editUrl(false);
- return true;
- }
- }
- boolean ctrl = event.hasModifiers(KeyEvent.META_CTRL_ON);
- if (!ctrl && isTypingKey(event) && !mTitleBar.isEditingUrl()) {
- editUrl(true);
- return mContentView.dispatchKeyEvent(event);
+ if (mActiveTab != null) {
+ WebView web = mActiveTab.getWebView();
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (code) {
+ case KeyEvent.KEYCODE_TAB:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if ((web != null) && web.hasFocus() && !mTitleBar.hasFocus()) {
+ editUrl(false);
+ return true;
+ }
+ }
+ boolean ctrl = event.hasModifiers(KeyEvent.META_CTRL_ON);
+ if (!ctrl && isTypingKey(event) && !mTitleBar.isEditingUrl()) {
+ editUrl(true);
+ return mContentView.dispatchKeyEvent(event);
+ }
}
}
return false;
@@ -493,4 +552,15 @@ public class XLargeUi extends BaseUi implements ScrollListener {
public void registerDropdownChangeListener(DropdownChangeListener d) {
mTitleBar.registerDropdownChangeListener(d);
}
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ if (mUseQuickControls) {
+ mPieControl.onMenuOpened(menu);
+ return false;
+ } else {
+ return true;
+ }
+ }
+
}
diff --git a/src/com/android/browser/autocomplete/SuggestiveAutoCompleteTextView.java b/src/com/android/browser/autocomplete/SuggestiveAutoCompleteTextView.java
index e8ca980..e51a629 100644
--- a/src/com/android/browser/autocomplete/SuggestiveAutoCompleteTextView.java
+++ b/src/com/android/browser/autocomplete/SuggestiveAutoCompleteTextView.java
@@ -782,6 +782,7 @@ public class SuggestiveAutoCompleteTextView extends EditText implements Filter.F
super.setText(text, type);
} else {
buffer.replace(0, buffer.length(), text);
+ invalidate();
}
}
@@ -790,7 +791,21 @@ public class SuggestiveAutoCompleteTextView extends EditText implements Filter.F
setText(text);
} else {
mBlockCompletion = true;
+ // If cursor movement handling was suspended (the view is
+ // not in focus), resume it and apply the pending change.
+ // Since we don't want to perform any filtering, this change
+ // is safe.
+ boolean wasSuspended = false;
+ if (mController.isCursorHandlingSuspended()) {
+ mController.resumeCursorMovementHandlingAndApplyChanges();
+ wasSuspended = true;
+ }
+
setText(text);
+
+ if (wasSuspended) {
+ mController.suspendCursorMovementHandling();
+ }
mBlockCompletion = false;
}
}
diff --git a/src/com/android/browser/preferences/GeneralPreferencesFragment.java b/src/com/android/browser/preferences/GeneralPreferencesFragment.java
index 9c763e9..d64f062 100644
--- a/src/com/android/browser/preferences/GeneralPreferencesFragment.java
+++ b/src/com/android/browser/preferences/GeneralPreferencesFragment.java
@@ -30,35 +30,19 @@ import android.accounts.AccountManagerFuture;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
-import android.app.Fragment;
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.RemoteException;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.provider.BrowserContract;
-import android.provider.BrowserContract.Bookmarks;
-import android.provider.BrowserContract.ChromeSyncColumns;
import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.LinearLayout;
-
-import java.util.ArrayList;
public class GeneralPreferencesFragment extends PreferenceFragment
implements OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
@@ -69,6 +53,7 @@ public class GeneralPreferencesFragment extends PreferenceFragment
Preference mChromeSync;
boolean mEnabled;
SharedPreferences mSharedPrefs;
+ Account[] mAccounts;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -144,13 +129,9 @@ public class GeneralPreferencesFragment extends PreferenceFragment
String name = bundle.getString(AccountManager.KEY_ACCOUNT_NAME);
String type = bundle.getString(AccountManager.KEY_ACCOUNT_TYPE);
Account account = new Account(name, type);
- Fragment frag = new ImportWizardDialog();
- Bundle extras = mChromeSync.getExtras();
- extras.putParcelableArray("accounts", new Account[] { account });
- frag.setArguments(extras);
- getFragmentManager().beginTransaction()
- .add(frag, null)
- .commit();
+ mAccounts = new Account[] { account };
+ ImportWizard wizard = ImportWizard.newInstance(mAccounts);
+ wizard.show(getFragmentManager(), null);
} catch (Exception ex) {
// Canceled or failed to login, doesn't matter to us
}
@@ -186,6 +167,7 @@ public class GeneralPreferencesFragment extends PreferenceFragment
}
} else {
// Google accounts are present.
+ mAccounts = accounts;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
Bundle args = mChromeSync.getExtras();
args.putParcelableArray("accounts", accounts);
@@ -226,16 +208,18 @@ public class GeneralPreferencesFragment extends PreferenceFragment
@Override
public boolean onPreferenceClick(Preference preference) {
- Fragment frag;
+ if (mAccounts == null) {
+ Log.w(TAG, "NULL accounts!");
+ return true;
+ }
+ DialogFragment frag;
if (mEnabled) {
frag = new AccountChooserDialog();
+ frag.setArguments(preference.getExtras());
} else {
- frag = new ImportWizardDialog();
+ frag = ImportWizard.newInstance(mAccounts);
}
- frag.setArguments(preference.getExtras());
- getFragmentManager().beginTransaction()
- .add(frag, null)
- .commit();
+ frag.show(getFragmentManager(), null);
return true;
}
@@ -276,196 +260,4 @@ public class GeneralPreferencesFragment extends PreferenceFragment
dismiss();
}
}
-
- public static class ImportWizardDialog extends DialogFragment implements OnClickListener {
- View mRemoveButton;
- View mCancelButton;
- String mDefaultAccount;
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- Context context = getActivity();
- Dialog dialog = new Dialog(context);
- dialog.setTitle(R.string.import_bookmarks_dialog_title);
- dialog.setContentView(R.layout.import_bookmarks_dialog);
- mRemoveButton = dialog.findViewById(R.id.remove);
- mRemoveButton.setOnClickListener(this);
- mCancelButton = dialog.findViewById(R.id.cancel);
- mCancelButton.setOnClickListener(this);
-
- LayoutInflater inflater = dialog.getLayoutInflater();
- LinearLayout accountList = (LinearLayout) dialog.findViewById(R.id.accountList);
- Account[] accounts = (Account[]) getArguments().getParcelableArray("accounts");
- mDefaultAccount = accounts[0].name;
- int length = accounts.length;
- for (int i = 0; i < length; i++) {
- Button button = (Button) inflater.inflate(R.layout.import_bookmarks_dialog_button,
- null);
- button.setText(context.getString(R.string.import_bookmarks_dialog_import,
- accounts[i].name));
- button.setTag(accounts[i].name);
- button.setOnClickListener(this);
- accountList.addView(button);
- }
-
- return dialog;
- }
-
- @Override
- public void onClick(View view) {
- if (view == mCancelButton) {
- dismiss();
- return;
- }
-
- ContentResolver resolver = getActivity().getContentResolver();
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
- String accountName;
- if (view == mRemoveButton) {
- // The user chose to remove their old bookmarks, delete them now
- resolver.delete(Bookmarks.CONTENT_URI,
- Bookmarks.PARENT + "=1 AND " + Bookmarks.ACCOUNT_NAME + " IS NULL", null);
- accountName = mDefaultAccount;
- } else {
- // The user chose to migrate their old bookmarks to the account they're syncing
- accountName = view.getTag().toString();
- migrateBookmarks(resolver, accountName);
- }
-
- // Record the fact that we turned on sync
- BrowserContract.Settings.setSyncEnabled(getActivity(), true);
- prefs.edit()
- .putString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, "com.google")
- .putString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, accountName)
- .apply();
-
- // Enable bookmark sync on all accounts
- Account[] accounts = (Account[]) getArguments().getParcelableArray("accounts");
- for (Account account : accounts) {
- if (ContentResolver.getIsSyncable(account, BrowserContract.AUTHORITY) == 0) {
- // Account wasn't syncable, enable it
- ContentResolver.setIsSyncable(account, BrowserContract.AUTHORITY, 1);
- ContentResolver.setSyncAutomatically(account, BrowserContract.AUTHORITY, true);
- }
- }
-
- dismiss();
- }
-
- /**
- * Migrates bookmarks to the given account
- */
- void migrateBookmarks(ContentResolver resolver, String accountName) {
- Cursor cursor = null;
- try {
- // Re-parent the bookmarks in the default root folder
- cursor = resolver.query(Bookmarks.CONTENT_URI, new String[] { Bookmarks._ID },
- Bookmarks.ACCOUNT_NAME + " =? AND " +
- ChromeSyncColumns.SERVER_UNIQUE + " =?",
- new String[] { accountName,
- ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR },
- null);
- ContentValues values = new ContentValues();
- if (cursor == null || !cursor.moveToFirst()) {
- // The root folders don't exist for the account, create them now
- ArrayList<ContentProviderOperation> ops =
- new ArrayList<ContentProviderOperation>();
-
- // Chrome sync root folder
- values.clear();
- values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_ROOT);
- values.put(Bookmarks.TITLE, "Google Chrome");
- values.put(Bookmarks.POSITION, 0);
- values.put(Bookmarks.IS_FOLDER, true);
- values.put(Bookmarks.DIRTY, true);
- ops.add(ContentProviderOperation.newInsert(
- Bookmarks.CONTENT_URI.buildUpon().appendQueryParameter(
- BrowserContract.CALLER_IS_SYNCADAPTER, "true").build())
- .withValues(values)
- .build());
-
- // Bookmarks folder
- values.clear();
- values.put(ChromeSyncColumns.SERVER_UNIQUE,
- ChromeSyncColumns.FOLDER_NAME_BOOKMARKS);
- values.put(Bookmarks.TITLE, "Bookmarks");
- values.put(Bookmarks.POSITION, 0);
- values.put(Bookmarks.IS_FOLDER, true);
- values.put(Bookmarks.DIRTY, true);
- ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI)
- .withValues(values)
- .withValueBackReference(Bookmarks.PARENT, 0)
- .build());
-
- // Bookmarks Bar folder
- values.clear();
- values.put(ChromeSyncColumns.SERVER_UNIQUE,
- ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR);
- values.put(Bookmarks.TITLE, "Bookmarks Bar");
- values.put(Bookmarks.POSITION, 0);
- values.put(Bookmarks.IS_FOLDER, true);
- values.put(Bookmarks.DIRTY, true);
- ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI)
- .withValues(values)
- .withValueBackReference(Bookmarks.PARENT, 1)
- .build());
-
- // Other Bookmarks folder
- values.clear();
- values.put(ChromeSyncColumns.SERVER_UNIQUE,
- ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS);
- values.put(Bookmarks.TITLE, "Other Bookmarks");
- values.put(Bookmarks.POSITION, 1000);
- values.put(Bookmarks.IS_FOLDER, true);
- values.put(Bookmarks.DIRTY, true);
- ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI)
- .withValues(values)
- .withValueBackReference(Bookmarks.PARENT, 1)
- .build());
-
- // Re-parent the existing bookmarks to the newly create bookmarks bar folder
- ops.add(ContentProviderOperation.newUpdate(Bookmarks.CONTENT_URI)
- .withValueBackReference(Bookmarks.PARENT, 2)
- .withSelection(Bookmarks.ACCOUNT_NAME + " IS NULL AND " +
- Bookmarks.PARENT + "=?",
- new String[] { Integer.toString(1) })
- .build());
-
- // Mark all non-root folder items as belonging to the new account
- values.clear();
- values.put(Bookmarks.ACCOUNT_TYPE, "com.google");
- values.put(Bookmarks.ACCOUNT_NAME, accountName);
- ops.add(ContentProviderOperation.newUpdate(Bookmarks.CONTENT_URI)
- .withValues(values)
- .withSelection(Bookmarks.ACCOUNT_NAME + " IS NULL AND " +
- Bookmarks._ID + "<>1", null)
- .build());
-
- try {
- resolver.applyBatch(BrowserContract.AUTHORITY, ops);
- } catch (RemoteException e) {
- Log.e(TAG, "failed to create root folder for account " + accountName, e);
- return;
- } catch (OperationApplicationException e) {
- Log.e(TAG, "failed to create root folder for account " + accountName, e);
- return;
- }
- } else {
- values.put(Bookmarks.PARENT, cursor.getLong(0));
- resolver.update(Bookmarks.CONTENT_URI, values, Bookmarks.PARENT + "=?",
- new String[] { Integer.toString(1) });
-
- // Mark all bookmarks at all levels as part of the new account
- values.clear();
- values.put(Bookmarks.ACCOUNT_TYPE, "com.google");
- values.put(Bookmarks.ACCOUNT_NAME, accountName);
- resolver.update(Bookmarks.CONTENT_URI, values,
- Bookmarks.ACCOUNT_NAME + " IS NULL AND " + Bookmarks._ID + "<>1",
- null);
- }
- } finally {
- if (cursor != null) cursor.close();
- }
- }
- }
}
diff --git a/src/com/android/browser/preferences/ImportWizard.java b/src/com/android/browser/preferences/ImportWizard.java
new file mode 100644
index 0000000..7105f4d
--- /dev/null
+++ b/src/com/android/browser/preferences/ImportWizard.java
@@ -0,0 +1,491 @@
+/*
+ * 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 com.android.browser.preferences;
+
+import com.android.browser.BrowserBookmarksPage;
+import com.android.browser.R;
+import com.android.browser.view.EventRedirectingFrameLayout;
+
+import android.accounts.Account;
+import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnKeyListener;
+import android.content.DialogInterface.OnShowListener;
+import android.content.OperationApplicationException;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Bookmarks;
+import android.provider.BrowserContract.ChromeSyncColumns;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class ImportWizard extends DialogFragment implements OnClickListener,
+ OnItemClickListener {
+
+ static final String TAG = "BookmarkImportWizard";
+
+ static final int PAGE_IMPORT_OR_DELETE = 0;
+ static final int PAGE_SELECT_ACCOUNT = 1;
+ static final int PAGE_CONFIRMATION = 2;
+
+ static final String STATE_CURRENT_PAGE = "wizard.current_page";
+ static final String STATE_IMPORT_OR_DELETE = "wizard.import_or_delete";
+ static final String STATE_SELECTED_ACCOUNT = "wizard.selected_account";
+
+ static final String ARG_ACCOUNTS = "accounts";
+
+ AlertDialog mDialog;
+ EventRedirectingFrameLayout mPages;
+ int mCurrentPage;
+ Button mPositiveButton, mNegativeButton;
+ ListView mImportOrDelete, mSelectAccount;
+ Account[] mAccounts;
+ TextView mSelectAccountDescription, mConfirmation;
+
+ static ImportWizard newInstance(Account[] accounts) {
+ ImportWizard wizard = new ImportWizard();
+ Bundle args = new Bundle();
+ args.putParcelableArray(ARG_ACCOUNTS, accounts);
+ wizard.setArguments(args);
+ return wizard;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAccounts = (Account[]) getArguments().getParcelableArray(ARG_ACCOUNTS);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ mDialog = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.import_bookmarks_dialog_title)
+ .setView(createView(savedInstanceState))
+ .setPositiveButton("?", null) // This is just a placeholder
+ .setNegativeButton("?", null) // Ditto
+ .setOnKeyListener(new OnKeyListener() {
+ @Override
+ public boolean onKey(DialogInterface arg0, int arg1, KeyEvent key) {
+ if (key.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ if (key.getAction() == KeyEvent.ACTION_UP
+ && !key.isCanceled()) {
+ mNegativeButton.performClick();
+ }
+ return true;
+ }
+ return false;
+ }
+ })
+ .create();
+ mDialog.setOnShowListener(new OnShowListener() {
+ @Override
+ public void onShow(DialogInterface dialog) {
+ mPositiveButton = mDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ mNegativeButton = mDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+ mPositiveButton.setOnClickListener(ImportWizard.this);
+ mNegativeButton.setOnClickListener(ImportWizard.this);
+ setupAnimations();
+ updateNavigation();
+ }
+ });
+ return mDialog;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(STATE_CURRENT_PAGE, mCurrentPage);
+ outState.putInt(STATE_IMPORT_OR_DELETE, mImportOrDelete.getCheckedItemPosition());
+ outState.putInt(STATE_SELECTED_ACCOUNT, mSelectAccount.getCheckedItemPosition());
+ }
+
+ public View createView(Bundle savedInstanceState) {
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+ View root = inflater.inflate(R.layout.bookmark_sync_wizard, null);
+ mPages = (EventRedirectingFrameLayout) root.findViewById(R.id.pages);
+ if (mPages.getChildCount() < 1) {
+ throw new IllegalStateException("no pages in wizard!");
+ }
+ if (savedInstanceState != null) {
+ mCurrentPage = savedInstanceState.getInt(STATE_CURRENT_PAGE);
+ } else {
+ mCurrentPage = 0;
+ }
+ setupPage1(savedInstanceState);
+ setupPage2(savedInstanceState);
+ setupPage3(savedInstanceState);
+ for (int i = 0; i < mPages.getChildCount(); i++) {
+ View v = mPages.getChildAt(i);
+ if (i <= mCurrentPage) {
+ preparePage();
+ v.setVisibility(View.VISIBLE);
+ } else {
+ v.setVisibility(View.GONE);
+ }
+ }
+ mPages.setTargetChild(mCurrentPage);
+ return root;
+ }
+
+ void setupPage1(Bundle savedInstanceState) {
+ mImportOrDelete = (ListView) mPages.findViewById(R.id.add_remove_bookmarks);
+ // Add an empty header so we get a divider above the list
+ mImportOrDelete.addHeaderView(new View(getActivity()));
+ Resources res = getActivity().getResources();
+ String[] choices = new String[] {
+ res.getString(R.string.import_bookmarks_dialog_add),
+ res.getString(R.string.import_bookmarks_dialog_remove)
+ };
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
+ R.layout.bookmark_sync_wizard_item, choices);
+ mImportOrDelete.setAdapter(adapter);
+ if (savedInstanceState != null) {
+ int position = savedInstanceState.getInt(STATE_IMPORT_OR_DELETE);
+ if (position == ListView.INVALID_POSITION) {
+ mImportOrDelete.clearChoices();
+ } else {
+ mImportOrDelete.setItemChecked(position, true);
+ }
+ }
+ mImportOrDelete.setOnItemClickListener(this);
+ }
+
+ void setupPage2(Bundle savedInstanceState) {
+ mSelectAccount = (ListView) mPages.findViewById(R.id.select_account);
+ mSelectAccountDescription =
+ (TextView) mPages.findViewById(R.id.select_account_description);
+ // Add an empty header so we get a divider above the list
+ mSelectAccount.addHeaderView(new View(getActivity()));
+ Resources res = getActivity().getResources();
+ String[] accountNames = new String[mAccounts.length];
+ for (int i = 0; i < mAccounts.length; i++) {
+ accountNames[i] = mAccounts[i].name;
+ }
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
+ R.layout.bookmark_sync_wizard_item, accountNames);
+ mSelectAccount.setAdapter(adapter);
+ mSelectAccount.setItemChecked(mSelectAccount.getHeaderViewsCount(), true);
+ if (savedInstanceState != null) {
+ int position = savedInstanceState.getInt(STATE_SELECTED_ACCOUNT);
+ if (position != ListView.INVALID_POSITION) {
+ mSelectAccount.setItemChecked(position, true);
+ }
+ }
+ mSelectAccount.setOnItemClickListener(this);
+ }
+
+ void setupPage3(Bundle savedInstanceState) {
+ mConfirmation = (TextView) mPages.findViewById(R.id.confirm);
+ }
+
+ void preparePage() {
+ switch (mCurrentPage) {
+ case PAGE_SELECT_ACCOUNT:
+ if (shouldDeleteBookmarks()) {
+ mSelectAccountDescription.setText(
+ R.string.import_bookmarks_dialog_delete_select_account);
+ } else {
+ mSelectAccountDescription.setText(
+ R.string.import_bookmarks_dialog_select_add_account);
+ }
+ break;
+ case PAGE_CONFIRMATION:
+ String account = getSelectedAccount().name;
+ String confirmationMessage;
+ if (shouldDeleteBookmarks()) {
+ confirmationMessage = getActivity().getString(
+ R.string.import_bookmarks_dialog_confirm_delete, account);
+ } else {
+ confirmationMessage = getActivity().getString(
+ R.string.import_bookmarks_dialog_confirm_add, account);
+ }
+ mConfirmation.setText(confirmationMessage);
+ break;
+ }
+ }
+
+ int getAdjustedCheckedItemPosition(ListView list) {
+ int position = list.getCheckedItemPosition();
+ if (position != ListView.INVALID_POSITION) {
+ position -= list.getHeaderViewsCount();
+ }
+ return position;
+ }
+
+ Account getSelectedAccount() {
+ return mAccounts[getAdjustedCheckedItemPosition(mSelectAccount)];
+ }
+
+ boolean shouldDeleteBookmarks() {
+ return getAdjustedCheckedItemPosition(mImportOrDelete) == 1;
+ }
+
+ @Override
+ public void onItemClick(
+ AdapterView<?> parent, View view, int position, long id) {
+ validate();
+ }
+
+ void updateNavigation() {
+ if (mCurrentPage == 0) {
+ mNegativeButton.setText(R.string.import_bookmarks_wizard_cancel);
+ } else {
+ mNegativeButton.setText(R.string.import_bookmarks_wizard_previous);
+ }
+ if ((mCurrentPage + 1) == mPages.getChildCount()) {
+ mPositiveButton.setText(R.string.import_bookmarks_wizard_done);
+ } else {
+ mPositiveButton.setText(R.string.import_bookmarks_wizard_next);
+ }
+ validate();
+ }
+
+ void validate() {
+ switch (mCurrentPage) {
+ case PAGE_IMPORT_OR_DELETE:
+ mPositiveButton.setEnabled(
+ mImportOrDelete.getCheckedItemPosition() != ListView.INVALID_POSITION);
+ break;
+ case PAGE_SELECT_ACCOUNT:
+ mPositiveButton.setEnabled(
+ mSelectAccount.getCheckedItemPosition() != ListView.INVALID_POSITION);
+ break;
+ }
+ }
+
+ void setupAnimations() {
+ float animX = mPages.getMeasuredWidth();
+ final LayoutTransition transitioner = new LayoutTransition();
+ ObjectAnimator appearing = ObjectAnimator.ofFloat(this, "translationX",
+ animX, 0);
+ ObjectAnimator disappearing = ObjectAnimator.ofFloat(this, "translationX",
+ 0, animX);
+ transitioner.setAnimator(LayoutTransition.APPEARING, appearing);
+ transitioner.setAnimator(LayoutTransition.DISAPPEARING, disappearing);
+ mPages.setLayoutTransition(transitioner);
+ }
+
+ boolean next() {
+ if (mCurrentPage + 1 < mPages.getChildCount()) {
+ mCurrentPage++;
+ preparePage();
+ mPages.getChildAt(mCurrentPage).setVisibility(View.VISIBLE);
+ mPages.setTargetChild(mCurrentPage);
+ return true;
+ }
+ return false;
+ }
+
+ boolean prev() {
+ if (mCurrentPage > 0) {
+ mPages.getChildAt(mCurrentPage).setVisibility(View.GONE);
+ mCurrentPage--;
+ mPages.setTargetChild(mCurrentPage);
+ return true;
+ }
+ return false;
+ }
+
+ void done() {
+ ContentResolver resolver = getActivity().getContentResolver();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ String accountName = getSelectedAccount().name;
+ if (shouldDeleteBookmarks()) {
+ // The user chose to remove their old bookmarks, delete them now
+ resolver.delete(Bookmarks.CONTENT_URI,
+ Bookmarks.PARENT + "=1 AND " + Bookmarks.ACCOUNT_NAME + " IS NULL", null);
+ } else {
+ // The user chose to migrate their old bookmarks to the account they're syncing
+ migrateBookmarks(resolver, accountName);
+ }
+
+ // Record the fact that we turned on sync
+ BrowserContract.Settings.setSyncEnabled(getActivity(), true);
+ prefs.edit()
+ .putString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, "com.google")
+ .putString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, accountName)
+ .apply();
+
+ // Enable bookmark sync on all accounts
+ Account[] accounts = (Account[]) getArguments().getParcelableArray("accounts");
+ for (Account account : accounts) {
+ if (ContentResolver.getIsSyncable(account, BrowserContract.AUTHORITY) == 0) {
+ // Account wasn't syncable, enable it
+ ContentResolver.setIsSyncable(account, BrowserContract.AUTHORITY, 1);
+ ContentResolver.setSyncAutomatically(account, BrowserContract.AUTHORITY, true);
+ }
+ }
+
+ dismiss();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mNegativeButton) {
+ if (prev()) {
+ updateNavigation();
+ } else {
+ dismiss();
+ }
+ } else if (v == mPositiveButton) {
+ if (next()) {
+ updateNavigation();
+ } else {
+ done();
+ }
+ }
+ }
+
+ /**
+ * Migrates bookmarks to the given account
+ */
+ void migrateBookmarks(ContentResolver resolver, String accountName) {
+ Cursor cursor = null;
+ try {
+ // Re-parent the bookmarks in the default root folder
+ cursor = resolver.query(Bookmarks.CONTENT_URI, new String[] { Bookmarks._ID },
+ Bookmarks.ACCOUNT_NAME + " =? AND " +
+ ChromeSyncColumns.SERVER_UNIQUE + " =?",
+ new String[] { accountName,
+ ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR },
+ null);
+ ContentValues values = new ContentValues();
+ if (cursor == null || !cursor.moveToFirst()) {
+ // The root folders don't exist for the account, create them now
+ ArrayList<ContentProviderOperation> ops =
+ new ArrayList<ContentProviderOperation>();
+
+ // Chrome sync root folder
+ values.clear();
+ values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_ROOT);
+ values.put(Bookmarks.TITLE, "Google Chrome");
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ ops.add(ContentProviderOperation.newInsert(
+ Bookmarks.CONTENT_URI.buildUpon().appendQueryParameter(
+ BrowserContract.CALLER_IS_SYNCADAPTER, "true").build())
+ .withValues(values)
+ .build());
+
+ // Bookmarks folder
+ values.clear();
+ values.put(ChromeSyncColumns.SERVER_UNIQUE,
+ ChromeSyncColumns.FOLDER_NAME_BOOKMARKS);
+ values.put(Bookmarks.TITLE, "Bookmarks");
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI)
+ .withValues(values)
+ .withValueBackReference(Bookmarks.PARENT, 0)
+ .build());
+
+ // Bookmarks Bar folder
+ values.clear();
+ values.put(ChromeSyncColumns.SERVER_UNIQUE,
+ ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR);
+ values.put(Bookmarks.TITLE, "Bookmarks Bar");
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI)
+ .withValues(values)
+ .withValueBackReference(Bookmarks.PARENT, 1)
+ .build());
+
+ // Other Bookmarks folder
+ values.clear();
+ values.put(ChromeSyncColumns.SERVER_UNIQUE,
+ ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS);
+ values.put(Bookmarks.TITLE, "Other Bookmarks");
+ values.put(Bookmarks.POSITION, 1000);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ ops.add(ContentProviderOperation.newInsert(Bookmarks.CONTENT_URI)
+ .withValues(values)
+ .withValueBackReference(Bookmarks.PARENT, 1)
+ .build());
+
+ // Re-parent the existing bookmarks to the newly create bookmarks bar folder
+ ops.add(ContentProviderOperation.newUpdate(Bookmarks.CONTENT_URI)
+ .withValueBackReference(Bookmarks.PARENT, 2)
+ .withSelection(Bookmarks.ACCOUNT_NAME + " IS NULL AND " +
+ Bookmarks.PARENT + "=?",
+ new String[] { Integer.toString(1) })
+ .build());
+
+ // Mark all non-root folder items as belonging to the new account
+ values.clear();
+ values.put(Bookmarks.ACCOUNT_TYPE, "com.google");
+ values.put(Bookmarks.ACCOUNT_NAME, accountName);
+ ops.add(ContentProviderOperation.newUpdate(Bookmarks.CONTENT_URI)
+ .withValues(values)
+ .withSelection(Bookmarks.ACCOUNT_NAME + " IS NULL AND " +
+ Bookmarks._ID + "<>1", null)
+ .build());
+
+ try {
+ resolver.applyBatch(BrowserContract.AUTHORITY, ops);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to create root folder for account " + accountName, e);
+ return;
+ } catch (OperationApplicationException e) {
+ Log.e(TAG, "failed to create root folder for account " + accountName, e);
+ return;
+ }
+ } else {
+ values.put(Bookmarks.PARENT, cursor.getLong(0));
+ resolver.update(Bookmarks.CONTENT_URI, values, Bookmarks.PARENT + "=?",
+ new String[] { Integer.toString(1) });
+
+ // Mark all bookmarks at all levels as part of the new account
+ values.clear();
+ values.put(Bookmarks.ACCOUNT_TYPE, "com.google");
+ values.put(Bookmarks.ACCOUNT_NAME, accountName);
+ resolver.update(Bookmarks.CONTENT_URI, values,
+ Bookmarks.ACCOUNT_NAME + " IS NULL AND " + Bookmarks._ID + "<>1",
+ null);
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ }
+}
diff --git a/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java b/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
index 2b2ee3e..2266608 100644
--- a/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
+++ b/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
@@ -17,14 +17,11 @@
package com.android.browser.preferences;
import com.android.browser.BrowserSettings;
-import com.android.browser.GoogleAccountLogin;
import com.android.browser.R;
-import android.accounts.Account;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
-import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
@@ -44,53 +41,11 @@ public class PrivacySecurityPreferencesFragment extends PreferenceFragment
Preference e = findPreference(BrowserSettings.PREF_CLEAR_HISTORY);
e.setOnPreferenceChangeListener(this);
- setupAutoLoginPreference();
}
@Override
public void onResume() {
super.onResume();
- setupAutoLoginPreference();
- }
-
- void setupAutoLoginPreference() {
- ListPreference autologinPref = (ListPreference) findPreference(
- BrowserSettings.PREF_AUTOLOGIN_ACCOUNT);
- autologinPref.setOnPreferenceChangeListener(this);
- updateAutoLoginSummary(autologinPref);
- Account[] accounts = GoogleAccountLogin.getAccounts(getActivity());
- // +1 for disable
- CharSequence[] names = new CharSequence[accounts.length + 1];
- CharSequence[] values = new CharSequence[names.length];
- int i = 0;
- int defaultAccount = 0;
- for (Account a : accounts) {
- values[i] = names[i] = a.name;
- i++;
- }
- names[i] = getResources().getString(R.string.pref_autologin_disable);
- values[i] = "";
- autologinPref.setEntries(names);
- autologinPref.setEntryValues(values);
- BrowserSettings bs = BrowserSettings.getInstance();
- if (bs.isAutoLoginEnabled()) {
- autologinPref.setValue(bs.getAutoLoginAccount(getActivity()));
- } else {
- autologinPref.setValue("");
- }
- }
-
- private void updateAutoLoginSummary(Preference pref) {
- if (!mSettings.isAutoLoginEnabled()) {
- pref.setSummary(R.string.pref_autologin_disable);
- } else {
- String account = mSettings.getAutoLoginAccount(getActivity());
- if (account == null) {
- pref.setSummary(R.string.pref_autologin_no_account);
- } else {
- pref.setSummary(getString(R.string.pref_autologin_summary, account));
- }
- }
}
@Override
@@ -102,17 +57,6 @@ public class PrivacySecurityPreferencesFragment extends PreferenceFragment
getActivity().setResult(Activity.RESULT_OK, (new Intent()).putExtra(Intent.EXTRA_TEXT,
pref.getKey()));
return true;
- } else if (pref.getKey().equals(BrowserSettings.PREF_AUTOLOGIN_ACCOUNT)) {
- String account = (String) objValue;
- if (account.length() == 0) {
- // Disable
- mSettings.setAutoLoginEnabled(getActivity(), false);
- } else {
- mSettings.setAutoLoginEnabled(getActivity(), true);
- }
- mSettings.setAutoLoginAccount(getActivity(), account);
- updateAutoLoginSummary(pref);
- return true;
}
return false;
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index e632fca..d154f20 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -1236,6 +1236,9 @@ public class BrowserProvider2 extends SQLiteContentProvider {
private boolean isValidParent(String accountType, String accountName,
long parentId) {
+ if (parentId <= 0) {
+ return false;
+ }
Uri uri = ContentUris.withAppendedId(Bookmarks.CONTENT_URI, parentId);
Cursor c = query(uri,
new String[] { Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_TYPE },
diff --git a/src/com/android/browser/view/BasePieView.java b/src/com/android/browser/view/BasePieView.java
new file mode 100644
index 0000000..ec02e3a
--- /dev/null
+++ b/src/com/android/browser/view/BasePieView.java
@@ -0,0 +1,161 @@
+/*
+ * 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 com.android.browser.view;
+
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Adapter;
+
+import java.util.ArrayList;
+
+/**
+ * common code for pie views
+ */
+public abstract class BasePieView implements PieMenu.PieView {
+
+ protected Adapter mAdapter;
+ private DataSetObserver mObserver;
+ protected ArrayList<View> mViews;
+
+ protected OnLayoutListener mListener;
+
+ protected int mCurrent;
+ protected int mChildWidth;
+ protected int mChildHeight;
+ protected int mWidth;
+ protected int mHeight;
+ protected int mLeft;
+ protected int mTop;
+
+ public BasePieView() {
+ }
+
+ public void setLayoutListener(OnLayoutListener l) {
+ mListener = l;
+ }
+
+ public void setAdapter(Adapter adapter) {
+ mAdapter = adapter;
+ if (adapter == null) {
+ if (mAdapter != null) {
+ mAdapter.unregisterDataSetObserver(mObserver);
+ }
+ mViews = null;
+ mCurrent = -1;
+ } else {
+ mObserver = new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ buildViews();
+ }
+
+ @Override
+ public void onInvalidated() {
+ mViews.clear();
+ }
+ };
+ mAdapter.registerDataSetObserver(mObserver);
+ setCurrent(0);
+ }
+ }
+
+ public void setCurrent(int ix) {
+ mCurrent = ix;
+ }
+
+ public Adapter getAdapter() {
+ return mAdapter;
+ }
+
+ protected void buildViews() {
+ if (mAdapter != null) {
+ final int n = mAdapter.getCount();
+ if (mViews == null) {
+ mViews = new ArrayList<View>(n);
+ } else {
+ mViews.clear();
+ }
+ mChildWidth = 0;
+ mChildHeight = 0;
+ for (int i = 0; i < n; i++) {
+ View view = mAdapter.getView(i, null, null);
+ view.measure(View.MeasureSpec.UNSPECIFIED,
+ View.MeasureSpec.UNSPECIFIED);
+ mChildWidth = Math.max(mChildWidth, view.getMeasuredWidth());
+ mChildHeight = Math.max(mChildHeight, view.getMeasuredHeight());
+ mViews.add(view);
+ }
+ }
+ }
+
+ /**
+ * this will be called before the first draw call
+ * needs to set top, left, width, height
+ */
+ @Override
+ public void layout(int anchorX, int anchorY, boolean left, float angle) {
+ if (mListener != null) {
+ mListener.onLayout(anchorX, anchorY, left);
+ }
+ }
+
+
+ @Override
+ public abstract void draw(Canvas canvas);
+
+ protected void drawView(View view, Canvas canvas) {
+ final int state = canvas.save();
+ canvas.translate(view.getLeft(), view.getTop());
+ view.draw(canvas);
+ canvas.restoreToCount(state);
+ }
+
+ protected abstract int findChildAt(int y);
+
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ int action = evt.getActionMasked();
+ int evtx = (int) evt.getX();
+ int evty = (int) evt.getY();
+ if ((evtx < mLeft) || (evtx >= mLeft + mWidth)
+ || (evty < mTop) || (evty >= mTop + mHeight)) {
+ return false;
+ }
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ View v = mViews.get(mCurrent);
+ setCurrent(Math.max(0, Math.min(mViews.size() -1,
+ findChildAt(evty))));
+ View v1 = mViews.get(mCurrent);
+ if (v != v1) {
+ v.setPressed(false);
+ v1.setPressed(true);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ mViews.get(mCurrent).performClick();
+ mViews.get(mCurrent).setPressed(false);
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+}
diff --git a/src/com/android/browser/view/EventRedirectingFrameLayout.java b/src/com/android/browser/view/EventRedirectingFrameLayout.java
new file mode 100644
index 0000000..901b021
--- /dev/null
+++ b/src/com/android/browser/view/EventRedirectingFrameLayout.java
@@ -0,0 +1,74 @@
+/*
+ * 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 com.android.browser.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+public class EventRedirectingFrameLayout extends FrameLayout {
+
+ private int mTargetChild;
+
+ public EventRedirectingFrameLayout(Context context) {
+ super(context);
+ }
+
+ public EventRedirectingFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public EventRedirectingFrameLayout(
+ Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setTargetChild(int index) {
+ if (index >= 0 && index < getChildCount()) {
+ mTargetChild = index;
+ getChildAt(mTargetChild).requestFocus();
+ }
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ View child = getChildAt(mTargetChild);
+ if (child != null)
+ return child.dispatchTouchEvent(ev);
+ return false;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ View child = getChildAt(mTargetChild);
+ if (child != null)
+ return child.dispatchKeyEvent(event);
+ return false;
+ }
+
+ @Override
+ public boolean dispatchKeyEventPreIme(KeyEvent event) {
+ View child = getChildAt(mTargetChild);
+ if (child != null)
+ return child.dispatchKeyEventPreIme(event);
+ return false;
+ }
+
+}
diff --git a/src/com/android/browser/view/PieItem.java b/src/com/android/browser/view/PieItem.java
new file mode 100644
index 0000000..3674447
--- /dev/null
+++ b/src/com/android/browser/view/PieItem.java
@@ -0,0 +1,109 @@
+/*
+ * 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 com.android.browser.view;
+
+import com.android.browser.view.PieMenu.PieView;
+
+import android.graphics.Path;
+import android.view.View;
+
+/**
+ * Pie menu item
+ */
+public class PieItem {
+
+ private View mView;
+ private PieView mPieView;
+ private int level;
+ private float start;
+ private float sweep;
+ private int inner;
+ private int outer;
+ private boolean mSelected;
+ private Path mPath;
+
+ public PieItem(View view, int level) {
+ mView = view;
+ this.level = level;
+ }
+
+ public PieItem(View view, int level, PieView sym) {
+ mView = view;
+ this.level = level;
+ mPieView = sym;
+ }
+
+ public void setSelected(boolean s) {
+ mSelected = s;
+ if (mView != null) {
+ mView.setSelected(s);
+ }
+ }
+
+ public boolean isSelected() {
+ return mSelected;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public void setGeometry(float st, float sw, int inside, int outside, Path p) {
+ start = st;
+ sweep = sw;
+ inner = inside;
+ outer = outside;
+ mPath = p;
+ }
+
+ public float getStartAngle() {
+ return start;
+ }
+
+ public float getSweep() {
+ return sweep;
+ }
+
+ public int getInnerRadius() {
+ return inner;
+ }
+
+ public int getOuterRadius() {
+ return outer;
+ }
+
+ public boolean isPieView() {
+ return (mPieView != null);
+ }
+
+ public View getView() {
+ return mView;
+ }
+
+ public void setPieView(PieView sym) {
+ mPieView = sym;
+ }
+
+ public PieView getPieView() {
+ return mPieView;
+ }
+
+ public Path getPath() {
+ return mPath;
+ }
+
+}
diff --git a/src/com/android/browser/view/PieListView.java b/src/com/android/browser/view/PieListView.java
new file mode 100644
index 0000000..04b512b
--- /dev/null
+++ b/src/com/android/browser/view/PieListView.java
@@ -0,0 +1,79 @@
+/*
+ * 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 com.android.browser.view;
+
+import com.android.browser.R;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.view.View;
+
+/**
+ * shows views in a menu style list
+ */
+public class PieListView extends BasePieView {
+
+ private Paint mBgPaint;
+
+ public PieListView(Context ctx) {
+ mBgPaint = new Paint();
+ mBgPaint.setColor(ctx.getResources().getColor(R.color.qcMenuBackground));
+ }
+
+ /**
+ * this will be called before the first draw call
+ */
+ @Override
+ public void layout(int anchorX, int anchorY, boolean left, float angle) {
+ super.layout(anchorX, anchorY, left, angle);
+ buildViews();
+ mWidth = mChildWidth;
+ mHeight = mChildHeight * mAdapter.getCount();
+ mLeft = anchorX + (left ? 0 : - mChildWidth);
+ mTop = anchorY - mHeight / 2;
+ if (mViews != null) {
+ layoutChildrenLinear();
+ }
+ }
+
+ protected void layoutChildrenLinear() {
+ final int n = mViews.size();
+ int top = mTop;
+ for (View view : mViews) {
+ view.layout(mLeft, top, mLeft + mChildWidth, top + mChildHeight);
+ top += mChildHeight;
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawRect(mLeft, mTop, mLeft + mWidth, mTop + mHeight, mBgPaint);
+ if (mViews != null) {
+ for (View view : mViews) {
+ drawView(view, canvas);
+ }
+ }
+ }
+
+ @Override
+ protected int findChildAt(int y) {
+ final int ix = (y - mTop) * mViews.size() / mHeight;
+ return ix;
+ }
+
+}
diff --git a/src/com/android/browser/view/PieMenu.java b/src/com/android/browser/view/PieMenu.java
index 080c257..cded435 100644
--- a/src/com/android/browser/view/PieMenu.java
+++ b/src/com/android/browser/view/PieMenu.java
@@ -20,17 +20,12 @@ import com.android.browser.R;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
import android.graphics.Canvas;
-import android.graphics.Matrix;
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.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
@@ -40,13 +35,11 @@ import android.view.ViewGroup;
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;
+ private static final int MAX_LEVELS = 5;
public interface PieController {
/**
@@ -55,31 +48,46 @@ public class PieMenu extends FrameLayout {
*/
public boolean onOpen();
}
+
+ /**
+ * A view like object that lives off of the pie menu
+ */
+ public interface PieView {
+
+ public interface OnLayoutListener {
+ public void onLayout(int ax, int ay, boolean left);
+ }
+
+ public void setLayoutListener(OnLayoutListener l);
+
+ public void layout(int anchorX, int anchorY, boolean onleft, float angle);
+
+ public void draw(Canvas c);
+
+ public boolean onTouchEvent(MotionEvent evt);
+
+ }
+
private Point mCenter;
private int mRadius;
private int mRadiusInc;
private int mSlop;
+ private int mTouchOffset;
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;
-
- private Drawable mActiveDrawable;
- private Drawable mInactiveDrawable;
- private final Paint mActiveShaderPaint = new Paint();
- private final Paint mInactiveShaderPaint = new Paint();
- private final Matrix mActiveMatrix = new Matrix();
- private final Matrix mInactiveMatrix = new Matrix();
+ private List<PieItem> mItems;
+ private int mLevels;
+ private int[] mCounts;
+ private PieView mPieView = null;
- private BitmapShader mActiveShader;
- private BitmapShader mInactiveShader;
+ private Drawable mBackground;
+ private Paint mNormalPaint;
+ private Paint mSelectedPaint;
+ // touch handling
+ PieItem mCurrentItem;
/**
* @param context
@@ -109,130 +117,66 @@ public class PieMenu extends FrameLayout {
}
private void init(Context ctx) {
- this.setTag(new MenuTag(0));
- mStack = new ArrayList<View>();
- mStack.add(this);
+ mItems = new ArrayList<PieItem>();
+ mLevels = 0;
+ mCounts = new int[MAX_LEVELS];
Resources res = ctx.getResources();
- mRadius = (int) res.getDimension(R.dimen.qc_radius);
- mRadiusInc = (int) res.getDimension(R.dimen.qc_radius_inc);
+ mRadius = (int) res.getDimension(R.dimen.qc_radius_start);
+ mRadiusInc = (int) res.getDimension(R.dimen.qc_radius_increment);
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));
+ mTouchOffset = (int) res.getDimension(R.dimen.qc_touch_offset);
mOpen = false;
- mMenu = new HashMap<View, List<View>>();
setWillNotDraw(false);
setDrawingCacheEnabled(false);
mCenter = new Point(0,0);
- mDirty = true;
- mActiveShaderPaint.setStyle(Paint.Style.FILL);
- mActiveShaderPaint.setAntiAlias(true);
-
- mInactiveShaderPaint.setStyle(Paint.Style.FILL);
- mInactiveShaderPaint.setAntiAlias(true);
- mActiveDrawable = res.getDrawable(R.drawable.qc_background_selected);
- mInactiveDrawable = res.getDrawable(R.drawable.qc_background_normal);
-
- Bitmap activeTexture = getDrawableAsBitmap(mActiveDrawable,
- mActiveDrawable.getIntrinsicWidth(),
- mActiveDrawable.getIntrinsicHeight());
- Bitmap inactiveTexture = getDrawableAsBitmap(mInactiveDrawable,
- mInactiveDrawable.getIntrinsicWidth(),
- mInactiveDrawable.getIntrinsicHeight());
-
- mActiveShader = new BitmapShader(activeTexture,
- Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
- mActiveShaderPaint.setShader(mActiveShader);
-
- mInactiveShader = new BitmapShader(inactiveTexture,
- Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
- mInactiveShaderPaint.setShader(mInactiveShader);
-
- }
-
- private static Bitmap getDrawableAsBitmap(Drawable drawable, int width, int height) {
- Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas c = new Canvas(b);
- drawable.setBounds(0, 0, width, height);
- drawable.draw(c);
- return b;
+ mBackground = res.getDrawable(R.drawable.qc_background_normal);
+ mNormalPaint = new Paint();
+ mNormalPaint.setColor(res.getColor(R.color.qc_normal));
+ mNormalPaint.setAntiAlias(true);
+ mSelectedPaint = new Paint();
+ mSelectedPaint.setColor(res.getColor(R.color.qc_selected));
+ mSelectedPaint.setAntiAlias(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) {
+ public void addItem(PieItem item) {
// 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);
- }
- }
+ mItems.add(item);
+ int l = item.getLevel();
+ mLevels = Math.max(mLevels, l);
+ mCounts[l]++;
}
- public void clearItems(View parent) {
- List<View> subs = mMenu.remove(parent);
- if (subs != null) {
- for (View sub: subs) {
- clearItems(sub);
- }
- }
+ public void removeItem(PieItem item) {
+ mItems.remove(item);
}
public void clearItems() {
- mMenu.clear();
+ mItems.clear();
}
+ private boolean onTheLeft() {
+ return mCenter.x < mSlop;
+ }
- public void show(boolean show) {
+ /**
+ * guaranteed has center set
+ * @param show
+ */
+ private void show(boolean show) {
mOpen = show;
if (mOpen) {
if (mController != null) {
boolean changed = mController.onOpen();
}
- mDirty = true;
+ layoutPie();
}
if (!show) {
- // hide sub items
- mStack.clear();
- mStack.add(this);
+ mCurrentItem = null;
+ mPieView = null;
}
invalidate();
}
@@ -246,151 +190,121 @@ public class PieMenu extends FrameLayout {
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);
+ private void layoutPie() {
+ float emptyangle = (float) Math.PI / 16;
+ int rgap = 2;
+ int inner = mRadius + rgap;
+ int outer = mRadius + mRadiusInc - rgap;
+ int radius = mRadius;
+ int gap = 1;
+ for (int i = 0; i < mLevels; i++) {
+ int level = i + 1;
+ float sweep = (float) (Math.PI - 2 * emptyangle) / mCounts[level];
+ float angle = emptyangle + sweep / 2;
+ for (PieItem item : mItems) {
+ if (item.getLevel() == level) {
+ View view = item.getView();
+ view.measure(view.getLayoutParams().width,
+ view.getLayoutParams().height);
+ int w = view.getMeasuredWidth();
+ int h = view.getMeasuredHeight();
+ int r = inner + (outer - inner) * 2 / 3;
+ int x = (int) (r * Math.sin(angle));
+ int y = mCenter.y - (int) (r * Math.cos(angle)) - h / 2;
+ if (onTheLeft()) {
+ x = mCenter.x + x - w / 2;
+ } else {
+ x = mCenter.x - x - w / 2;
+ }
+ view.layout(x, y, x + w, y + h);
+ float itemstart = angle - sweep / 2;
+ Path slice = makeSlice(getDegrees(itemstart) - gap,
+ getDegrees(itemstart + sweep) + gap,
+ outer, inner, mCenter);
+ item.setGeometry(itemstart, sweep, inner, outer, slice);
+ angle += sweep;
}
- anchor = drawSlices(canvas, subs, radius, angles.x, angles.y);
- radius += mRadiusInc + RADIUS_GAP;
}
- canvas.restoreToCount(state);
- mDirty = false;
+ inner += mRadiusInc;
+ outer += mRadiusInc;
}
}
+
/**
- * draw the set of slices
- * @param canvas
- * @param items
- * @param radius
- * @param start
- * @param sweep
- * @return the angle of the selected slice
+ * converts a
+ *
+ * @param angle from 0..PI to Android degrees (clockwise starting at 3
+ * o'clock)
+ * @return skia angle
*/
- 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;
- int state = canvas.save();
- int[] topLeft = new int[2];
- getLocationInWindow(topLeft);
- topLeft[0] = mCenter.x - outer;
- topLeft[1] = mCenter.y - outer;
- Paint paint = item.isPressed() ? mActiveShaderPaint : mInactiveShaderPaint;
- drawClipped(canvas, paint, slice, topLeft, item.isPressed());
- canvas.restoreToCount(state);
+ private float getDegrees(double angle) {
+ return (float) (270 - 180 * angle / Math.PI);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mOpen) {
+ int w = mBackground.getIntrinsicWidth();
+ int h = mBackground.getIntrinsicHeight();
+ int left = mCenter.x - w;
+ int top = mCenter.y - h / 2;
+ mBackground.setBounds(left, top, left + w, top + h);
+ int state;
state = canvas.save();
if (onTheLeft()) {
canvas.scale(-1, 1);
}
- canvas.translate(item.getX(), item.getY());
- item.draw(canvas);
+ mBackground.draw(canvas);
canvas.restoreToCount(state);
- if (mStack.contains(item)) {
- // item is anchor for sub menu
- newanchor = angle;
+ for (PieItem item : mItems) {
+ Paint p = item.isSelected() ? mSelectedPaint : mNormalPaint;
+ state = canvas.save();
+ if (onTheLeft()) {
+ canvas.scale(-1, 1);
+ }
+ drawPath(canvas, item.getPath(), p);
+ canvas.restoreToCount(state);
+ drawItem(canvas, item);
+ }
+ if (mPieView != null) {
+ mPieView.draw(canvas);
}
- angle += sweep;
}
- return newanchor;
}
- private void drawClipped(Canvas canvas, Paint paint, Path clipPath, int[] pos,
- boolean selected) {
- // TODO: We should change the matrix/shader only when needed
- final Matrix matrix = selected ? mActiveMatrix : mInactiveMatrix;
- matrix.setTranslate(pos[0], pos[1]);
- (selected ? mActiveShader : mInactiveShader).setLocalMatrix(matrix);
- canvas.drawPath(clipPath, paint);
+ private void drawItem(Canvas canvas, PieItem item) {
+ int outer = item.getOuterRadius();
+ int left = mCenter.x - outer;
+ int top = mCenter.y - outer;
+ // draw the item view
+ View view = item.getView();
+ int state = canvas.save();
+ canvas.translate(view.getX(), view.getY());
+ view.draw(canvas);
+ canvas.restoreToCount(state);
}
-
- /**
- * 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 void drawPath(Canvas canvas, Path path, Paint paint) {
+ canvas.drawPath(path, paint);
}
- 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);
+ private Path makeSlice(float start, float end, int outer, int inner, Point center) {
+ RectF bb =
+ new RectF(center.x - outer, center.y - outer, center.x + outer,
+ center.y + outer);
+ RectF bbi =
+ new RectF(center.x - inner, center.y - inner, center.x + inner,
+ center.y + inner);
Path path = new Path();
- path.arcTo(bb, startangle, endangle - startangle, true);
- path.arcTo(bbi, endangle, startangle - endangle);
+ path.arcTo(bb, start, end - start, true);
+ path.arcTo(bbi, end, start - end);
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();
@@ -405,12 +319,16 @@ public class PieMenu extends FrameLayout {
}
} else if (MotionEvent.ACTION_UP == action) {
if (mOpen) {
- View v = mCurrentView;
- deselect();
- if (v != null) {
- v.performClick();
+ boolean handled = false;
+ if (mPieView != null) {
+ handled = mPieView.onTouchEvent(evt);
}
+ PieItem item = mCurrentItem;
+ deselect();
show(false);
+ if (!handled && (item != null)) {
+ item.getView().performClick();
+ }
return true;
}
} else if (MotionEvent.ACTION_CANCEL == action) {
@@ -420,19 +338,36 @@ public class PieMenu extends FrameLayout {
deselect();
return false;
} else if (MotionEvent.ACTION_MOVE == action) {
+ boolean handled = false;
PointF polar = getPolar(x, y);
- if (polar.y > mRadius + 2 * mRadiusInc) {
- show(false);
+ int maxr = mRadius + mLevels * mRadiusInc + 50;
+ if (mPieView != null) {
+ handled = mPieView.onTouchEvent(evt);
+ }
+ if (handled) {
+ invalidate();
+ return false;
+ }
+ if (polar.y > maxr) {
deselect();
+ show(false);
evt.setAction(MotionEvent.ACTION_DOWN);
if (getParent() != null) {
((ViewGroup) getParent()).dispatchTouchEvent(evt);
}
return false;
}
- View v = findView(polar);
- if (mCurrentView != v) {
- onEnter(v);
+ PieItem item = findItem(polar);
+ if (mCurrentItem != item) {
+ onEnter(item);
+ if ((item != null) && item.isPieView()) {
+ int cx = item.getView().getLeft() + (onTheLeft()
+ ? item.getView().getWidth() : 0);
+ int cy = item.getView().getTop();
+ mPieView = item.getPieView();
+ layoutPieView(mPieView, cx, cy,
+ (item.getStartAngle() + item.getSweep()) / 2);
+ }
invalidate();
}
}
@@ -440,53 +375,35 @@ public class PieMenu extends FrameLayout {
return false;
}
+ private void layoutPieView(PieView pv, int x, int y, float angle) {
+ pv.layout(x, y, onTheLeft(), angle);
+ }
+
/**
* enter a slice for a view
* updates model only
- * @param view
+ * @param item
*/
- private void onEnter(View view) {
+ private void onEnter(PieItem item) {
// deselect
- if (mCurrentView != null) {
- if (getLevel(mCurrentView) >= getLevel(view)) {
- mCurrentView.setPressed(false);
- }
+ if (mCurrentItem != null) {
+ mCurrentItem.setSelected(false);
}
- if (view != null) {
+ if (item != null) {
// clear up stack
playSoundEffect(SoundEffectConstants.CLICK);
- 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);
+ item.setSelected(true);
+ mPieView = null;
}
- mCurrentView = view;
+ mCurrentItem = item;
}
private void deselect() {
- if (mCurrentView != null) {
- mCurrentView.setPressed(false);
+ if (mCurrentItem != null) {
+ mCurrentItem.setSelected(false);
}
- mCurrentView = null;
- }
-
- private int getLevel(View v) {
- if (v == null) return -1;
- return ((MenuTag) v.getTag()).level;
+ mCurrentItem = null;
+ mPieView = null;
}
private PointF getPolar(float x, float y) {
@@ -510,39 +427,19 @@ public class PieMenu extends FrameLayout {
/**
*
* @param polar x: angle, y: dist
- * @return
+ * @return the item at angle/dist or null
*/
- private View findView(PointF polar) {
+ private PieItem findItem(PointF polar) {
// 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 < polar.y)
- && (tag.outer > polar.y)
- && (tag.start < polar.x)
- && (tag.start + tag.sweep > polar.x)) {
- return item;
- }
- }
+ for (PieItem item : mItems) {
+ if ((item.getInnerRadius() - mTouchOffset < polar.y)
+ && (item.getOuterRadius() - mTouchOffset > polar.y)
+ && (item.getStartAngle() < polar.x)
+ && (item.getStartAngle() + item.getSweep() > polar.x)) {
+ return item;
}
}
return null;
}
- class MenuTag {
-
- int level;
- float start;
- float sweep;
- int inner;
- int outer;
-
- public MenuTag(int l) {
- level = l;
- }
-
- }
-
}
diff --git a/src/com/android/browser/view/PieStackView.java b/src/com/android/browser/view/PieStackView.java
new file mode 100644
index 0000000..ca641f9
--- /dev/null
+++ b/src/com/android/browser/view/PieStackView.java
@@ -0,0 +1,105 @@
+/*
+ * 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 com.android.browser.view;
+
+import com.android.browser.R;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.view.View;
+
+/**
+ * shows views in a stack
+ */
+public class PieStackView extends BasePieView {
+
+ private static final int SLOP = 5;
+
+ private OnCurrentListener mCurrentListener;
+ private int mMinHeight;
+
+ public interface OnCurrentListener {
+ public void onSetCurrent(int index);
+ }
+
+ public PieStackView(Context ctx) {
+ mMinHeight = (int) ctx.getResources()
+ .getDimension(R.dimen.qc_tab_title_height);
+ }
+
+ public void setOnCurrentListener(OnCurrentListener l) {
+ mCurrentListener = l;
+ }
+
+ @Override
+ public void setCurrent(int ix) {
+ super.setCurrent(ix);
+ if (mCurrentListener != null) {
+ mCurrentListener.onSetCurrent(ix);
+ buildViews();
+ layoutChildrenLinear();
+ }
+ }
+
+ /**
+ * this will be called before the first draw call
+ */
+ @Override
+ public void layout(int anchorX, int anchorY, boolean left, float angle) {
+ super.layout(anchorX, anchorY, left, angle);
+ buildViews();
+ mWidth = mChildWidth;
+ mHeight = mChildHeight + (mViews.size() - 1) * mMinHeight;
+ mLeft = anchorX + (left ? SLOP : -(SLOP + mChildWidth));
+ mTop = anchorY - mHeight / 2;
+ if (mViews != null) {
+ layoutChildrenLinear();
+ }
+ }
+
+ private void layoutChildrenLinear() {
+ final int n = mViews.size();
+ int top = mTop;
+ int dy = (n == 1) ? 0 : (mHeight - mChildHeight) / (n - 1);
+ for (View view : mViews) {
+ int x = mLeft;
+ view.layout(x, top, x + mChildWidth, top + mChildHeight);
+ top += dy;
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mViews != null) {
+ final int n = mViews.size();
+ for (int i = 0; i < mCurrent; i++) {
+ drawView(mViews.get(i), canvas);
+ }
+ for (int i = n - 1; i > mCurrent; i--) {
+ drawView(mViews.get(i), canvas);
+ }
+ drawView(mViews.get(mCurrent), canvas);
+ }
+ }
+
+ @Override
+ protected int findChildAt(int y) {
+ final int ix = (y - mTop) * mViews.size() / mHeight;
+ return ix;
+ }
+
+}