diff options
Diffstat (limited to 'src/com/android')
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; + } + +} |