import java.util.regex.Pattern;
public class BrowserActivity extends Activity
- implements View.OnCreateContextMenuListener, DownloadListener,
- AccountManagerCallback<Account[]> {
+ implements View.OnCreateContextMenuListener, DownloadListener {
/* Define some aliases to make these debugging flags easier to refer to.
* This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG".
private static final int SHORTCUT_DICTIONARY_SEARCH = 3;
private static final int SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH = 4;
- private Account[] mAccountsGoogle;
- private Account[] mAccountsPreferHosted;
-
- // XXX: These constants should be exposed through some public api. Hardcode
- // the values for now until some solution for gsf can be worked out.
- // http://b/issue?id=2425179
- private static final String ACCOUNT_TYPE = "com.google";
- private static final String FEATURE_LEGACY_GOOGLE = "legacy_google";
- private static final String FEATURE_LEGACY_HOSTED_OR_GOOGLE =
- "legacy_hosted_or_google";
-
- private void startReadOfGoogleAccounts() {
- mAccountsGoogle = null;
- mAccountsPreferHosted = null;
-
- AccountManager.get(this).getAccountsByTypeAndFeatures(
- ACCOUNT_TYPE, new String[]{ FEATURE_LEGACY_HOSTED_OR_GOOGLE },
- this, null);
- }
-
- /** This implements AccountManagerCallback<Account[]> */
- public void run(AccountManagerFuture<Account[]> accountManagerFuture) {
- try {
- if (mAccountsGoogle == null) {
- mAccountsGoogle = accountManagerFuture.getResult();
-
- AccountManager.get(this).getAccountsByTypeAndFeatures(
- ACCOUNT_TYPE, new String[]{ FEATURE_LEGACY_GOOGLE },
- this, null);
- } else {
- mAccountsPreferHosted = accountManagerFuture.getResult();
- setupHomePage();
- }
- } catch (OperationCanceledException e) {
- setupHomePage();
- } catch (IOException e) {
- setupHomePage();
- } catch (AuthenticatorException e) {
- setupHomePage();
- }
- }
-
- private void setupHomePage() {
- // get the default home page
- String homepage = mSettings.getHomePage();
-
- if (mAccountsPreferHosted != null && mAccountsGoogle != null) {
- // three cases:
- //
- // hostedUser == googleUser
- // The device has only a google account
- //
- // hostedUser != googleUser
- // The device has a hosted account and a google account
- //
- // hostedUser != null, googleUser == null
- // The device has only a hosted account (so far)
- String hostedUser = mAccountsPreferHosted.length == 0
- ? null
- : mAccountsPreferHosted[0].name;
- String googleUser = mAccountsGoogle.length == 0 ? null : mAccountsGoogle[0].name;
-
- // developers might have no accounts at all
- if (hostedUser == null) return;
-
- if (googleUser == null || !hostedUser.equals(googleUser)) {
- String domain = hostedUser.substring(hostedUser.lastIndexOf('@')+1);
- homepage = homepage.replace("?", "/a/" + domain + "?");
- }
- }
-
- mSettings.setHomePage(BrowserActivity.this, homepage);
- resumeAfterCredentials();
- }
-
private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
@Override
public Void doInBackground(File... files) {
.findViewById(R.id.fullscreen_custom_content);
frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
mTitleBar = new TitleBar(this);
+ // mTitleBar will be always shown in the fully loaded mode
+ mTitleBar.setProgress(100);
mFakeTitleBar = new TitleBar(this);
// Create the tab control and our initial tab
// Keep a settings instance handy.
mSettings = BrowserSettings.getInstance();
mSettings.setTabControl(mTabControl);
- mSettings.loadFromDb(this);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
+ // Find out if the network is currently up.
+ ConnectivityManager cm = (ConnectivityManager) getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ NetworkInfo info = cm.getActiveNetworkInfo();
+ if (info != null) {
+ mIsNetworkUp = info.isAvailable();
+ }
+
/* enables registration for changes in network status from
http stack */
mNetworkStateChangedFilter = new IntentFilter();
webView.setInitialScale(scale);
}
}
- // If we are not restoring from an icicle, then there is a high
- // likely hood this is the first run. So, check to see if the
- // homepage needs to be configured and copy any plugins from our
- // asset directory to the data partition.
- if ((extra == null || !extra.getBoolean("testing"))
- && !mSettings.isLoginInitialized()) {
- startReadOfGoogleAccounts();
- }
if (urlData.isEmpty()) {
- if (mSettings.isLoginInitialized()) {
- loadUrl(webView, mSettings.getHomePage());
- } else {
- waitForCredentials();
- }
+ loadUrl(webView, mSettings.getHomePage());
} else {
loadUrlDataIn(t, urlData);
}
}
// Work out which packages are installed on the system.
getInstalledPackages();
+
+ // Start watching the default geolocation permissions
+ mSystemAllowGeolocationOrigins
+ = new SystemAllowGeolocationOrigins(getApplicationContext());
+ mSystemAllowGeolocationOrigins.start();
}
/**
bp.setQueryResults(mTabControl.getCurrentTab().getVoiceSearchResults());
client.release();
- startSearch(result, false,
- createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY),
- false);
+ Bundle bundle = createGoogleSearchSourceBundle(
+ GOOGLE_SEARCH_SOURCE_SEARCHKEY);
+ bundle.putBoolean(SearchManager.CONTEXT_IS_VOICE, true);
+ startSearch(result, false, bundle, false);
}
@Override
// just resume the browser
return;
}
+ // In case the SearchDialog is open.
+ ((SearchManager) getSystemService(Context.SEARCH_SERVICE))
+ .stopSearch();
boolean activateVoiceSearch = RecognizerResultsIntent
.ACTION_VOICE_SEARCH_RESULTS.equals(action);
if (Intent.ACTION_VIEW.equals(action)
final String appId = intent
.getStringExtra(Browser.EXTRA_APPLICATION_ID);
- if ((Intent.ACTION_VIEW.equals(action) || activateVoiceSearch)
+ if ((Intent.ACTION_VIEW.equals(action)
+ // If a voice search has no appId, it means that it came
+ // from the browser. In that case, reuse the current tab.
+ || (activateVoiceSearch && appId != null))
&& !getPackageName().equals(appId)
&& (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
Tab appTab = mTabControl.getTabFromId(appId);
// If the WebView has the same original url and is on that
// page, it can be reused.
boolean needsLoad =
- mTabControl.recreateWebView(appTab, urlData.mUrl);
+ mTabControl.recreateWebView(appTab, urlData);
if (current != appTab) {
switchToTab(mTabControl.getTabIndex(appTab));
return false;
}
- Browser.updateVisitedHistory(mResolver, url, false);
- Browser.addSearchUrl(mResolver, url);
+ final ContentResolver cr = mResolver;
+ final String newUrl = url;
+ new AsyncTask<Void, Void, Void>() {
+ protected Void doInBackground(Void... unused) {
+ Browser.updateVisitedHistory(cr, newUrl, false);
+ Browser.addSearchUrl(cr, newUrl);
+ return null;
+ }
+ }.execute();
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.addCategory(Intent.CATEGORY_DEFAULT);
// But currently, we get the user-typed URL from search box as well.
url = fixUrl(url);
url = smartUrlFilter(url);
- Browser.updateVisitedHistory(mResolver, url, false);
+ final ContentResolver cr = mResolver;
+ final String newUrl = url;
+ new AsyncTask<Void, Void, Void>() {
+ protected Void doInBackground(Void... unused) {
+ Browser.updateVisitedHistory(cr, newUrl, false);
+ return null;
+ }
+ }.execute();
String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
if (url.contains(searchSource)) {
String source = null;
mTitleBar.setInVoiceMode(false);
mFakeTitleBar.setInVoiceMode(false);
- mTitleBar.setDisplayTitle(mTitle);
- mFakeTitleBar.setDisplayTitle(mTitle);
+ mTitleBar.setDisplayTitle(mUrl);
+ mFakeTitleBar.setDisplayTitle(mUrl);
}
/* package */ static String fixUrl(String inUrl) {
// FIXME: Converting the url to lower case
mWakeLock.release();
}
- if (mCredsDlg != null) {
- if (!mHandler.hasMessages(CANCEL_CREDS_REQUEST)) {
- // In case credential request never comes back
- mHandler.sendEmptyMessageDelayed(CANCEL_CREDS_REQUEST, 6000);
- }
- }
-
registerReceiver(mNetworkStateIntentReceiver,
mNetworkStateChangedFilter);
WebView.enablePlatformNotifications();
private TitleBar mFakeTitleBar;
/**
- * Holder for the fake title bar. It will have a foreground shadow, as well
- * as a white background, so the fake title bar looks like the real one.
- */
- private ViewGroup mFakeTitleBarHolder;
-
- /**
- * Layout parameters for the fake title bar within mFakeTitleBarHolder
- */
- private FrameLayout.LayoutParams mFakeTitleBarParams
- = new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- /**
* Keeps track of whether the options menu is open. This is important in
* determining whether to show or hide the title bar overlay.
*/
return true;
}
- /**
- * Special class used exclusively for the shadow drawn underneath the fake
- * title bar. The shadow does not need to be drawn if the WebView
- * underneath is scrolled to the top, because it will draw directly on top
- * of the embedded shadow.
- */
- private static class Shadow extends View {
- private WebView mWebView;
-
- public Shadow(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public void setWebView(WebView view) {
- mWebView = view;
- }
-
- @Override
- public void draw(Canvas canvas) {
- // In general onDraw is the method to override, but we care about
- // whether or not the background gets drawn, which happens in draw()
- if (mWebView == null || mWebView.getScrollY() > getHeight()) {
- super.draw(canvas);
- }
- // Need to invalidate so that if the scroll position changes, we
- // still draw as appropriate.
- invalidate();
- }
- }
-
private void showFakeTitleBar() {
- final View decor = getWindow().peekDecorView();
if (mFakeTitleBar.getParent() == null && mActiveTabsPage == null
- && !mActivityInPause && decor != null
- && decor.getWindowToken() != null) {
- Rect visRect = new Rect();
- if (!mBrowserFrameLayout.getGlobalVisibleRect(visRect)) {
- if (LOGD_ENABLED) {
- Log.d(LOGTAG, "showFakeTitleBar visRect failed");
- }
+ && !mActivityInPause) {
+ WebView mainView = mTabControl.getCurrentWebView();
+ // if there is no current WebView, don't show the faked title bar;
+ if (mainView == null) {
return;
}
= new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.TOP;
- WebView mainView = mTabControl.getCurrentWebView();
- boolean atTop = mainView != null && mainView.getScrollY() == 0;
+ boolean atTop = mainView.getScrollY() == 0;
params.windowAnimations = atTop ? 0 : R.style.TitleBar;
- // XXX : Without providing an offset, the fake title bar will be
- // placed underneath the status bar. Use the global visible rect
- // of mBrowserFrameLayout to determine the bottom of the status bar
- params.y = visRect.top;
- // Add a holder for the title bar. It also holds a shadow to show
- // below the title bar.
- if (mFakeTitleBarHolder == null) {
- mFakeTitleBarHolder = (ViewGroup) LayoutInflater.from(this)
- .inflate(R.layout.title_bar_bg, null);
- }
- Shadow shadow = (Shadow) mFakeTitleBarHolder.findViewById(
- R.id.shadow);
- shadow.setWebView(mainView);
- mFakeTitleBarHolder.addView(mFakeTitleBar, 0, mFakeTitleBarParams);
- manager.addView(mFakeTitleBarHolder, params);
+ manager.addView(mFakeTitleBar, params);
}
}
private void hideFakeTitleBar() {
if (mFakeTitleBar.getParent() == null) return;
WindowManager.LayoutParams params = (WindowManager.LayoutParams)
- mFakeTitleBarHolder.getLayoutParams();
+ mFakeTitleBar.getLayoutParams();
WebView mainView = mTabControl.getCurrentWebView();
// Although we decided whether or not to animate based on the current
// scroll position, the scroll position may have changed since the
? 0 : R.style.TitleBar;
WindowManager manager
= (WindowManager) getSystemService(Context.WINDOW_SERVICE);
- manager.updateViewLayout(mFakeTitleBarHolder, params);
- mFakeTitleBarHolder.removeView(mFakeTitleBar);
- manager.removeView(mFakeTitleBarHolder);
+ manager.updateViewLayout(mFakeTitleBar, params);
+ manager.removeView(mFakeTitleBar);
}
/**
.obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
}
- // Clear the credentials toast if it is up
- if (mCredsDlg != null && mCredsDlg.isShowing()) {
- mCredsDlg.dismiss();
- }
- mCredsDlg = null;
-
// FIXME: This removes the active tabs page and resets the menu to
// MAIN_MENU. A better solution might be to do this work in onNewIntent
// but then we would need to save it in onSaveInstanceState and restore
WebIconDatabase.getInstance().close();
unregisterReceiver(mPackageInstallationReceiver);
+
+ // Stop watching the default geolocation permissions
+ mSystemAllowGeolocationOrigins.stop();
+ mSystemAllowGeolocationOrigins = null;
}
@Override
mTabControl.freeMemory();
}
- private boolean resumeWebViewTimers() {
+ private void resumeWebViewTimers() {
Tab tab = mTabControl.getCurrentTab();
+ if (tab == null) return; // monkey can trigger this
boolean inLoad = tab.inLoad();
if ((!mActivityInPause && !inLoad) || (mActivityInPause && inLoad)) {
CookieSyncManager.getInstance().startSync();
if (w != null) {
w.resumeTimers();
}
- return true;
- } else {
- return false;
}
}
}
}
- // FIXME: Do we want to call this when loading google for the first time?
- /*
- * This function is called when we are launching for the first time. We
- * are waiting for the login credentials before loading Google home
- * pages. This way the user will be logged in straight away.
- */
- private void waitForCredentials() {
- // Show a toast
- mCredsDlg = new ProgressDialog(this);
- mCredsDlg.setIndeterminate(true);
- mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg));
- // If the user cancels the operation, then cancel the Google
- // Credentials request.
- mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST));
- mCredsDlg.show();
-
- // We set a timeout for the retrieval of credentials in onResume()
- // as that is when we have freed up some CPU time to get
- // the login credentials.
- }
-
- /*
- * If we have received the credentials or we have timed out and we are
- * showing the credentials dialog, then it is time to move on.
- */
- private void resumeAfterCredentials() {
- if (mCredsDlg == null) {
- return;
- }
-
- // Clear the toast
- if (mCredsDlg.isShowing()) {
- mCredsDlg.dismiss();
- }
- mCredsDlg = null;
-
- // Clear any pending timeout
- mHandler.removeMessages(CANCEL_CREDS_REQUEST);
-
- // Load the page
- WebView w = mTabControl.getCurrentWebView();
- if (w != null) {
- loadUrl(w, mSettings.getHomePage());
- }
-
- // Update the settings, need to do this last as it can take a moment
- // to persist the settings. In the mean time we could be loading
- // content.
- mSettings.setLoginInitialized(this);
- }
-
// Open the icon database and retain all the icons for visited sites.
private void retainIconsOnStartup() {
final WebIconDatabase db = WebIconDatabase.getInstance();
db.open(getDir("icons", 0).getPath());
+ Cursor c = null;
try {
- Cursor c = Browser.getAllBookmarks(mResolver);
- if (!c.moveToFirst()) {
- c.deactivate();
- return;
+ c = Browser.getAllBookmarks(mResolver);
+ if (c.moveToFirst()) {
+ int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
+ do {
+ String url = c.getString(urlIndex);
+ db.retainIconForPageUrl(url);
+ } while (c.moveToNext());
}
- int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
- do {
- String url = c.getString(urlIndex);
- db.retainIconForPageUrl(url);
- } while (c.moveToNext());
- c.deactivate();
} catch (IllegalStateException e) {
Log.e(LOGTAG, "retainIconsOnStartup", e);
+ } finally {
+ if (c!= null) c.close();
}
}
// Message Ids
private static final int FOCUS_NODE_HREF = 102;
- private static final int CANCEL_CREDS_REQUEST = 103;
private static final int RELEASE_WAKELOCK = 107;
static final int UPDATE_BOOKMARK_THUMBNAIL = 108;
stopLoading();
break;
- case CANCEL_CREDS_REQUEST:
- resumeAfterCredentials();
- break;
-
case RELEASE_WAKELOCK:
if (mWakeLock.isHeld()) {
mWakeLock.release();
return null;
}
Bitmap bm = Bitmap.createBitmap(getDesiredThumbnailWidth(this),
- getDesiredThumbnailHeight(this), Bitmap.Config.ARGB_4444);
+ getDesiredThumbnailHeight(this), Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bm);
// May need to tweak these values to determine what is the
// best scale factor
// -------------------------------------------------------------------------
void onProgressChanged(WebView view, int newProgress) {
- mTitleBar.setProgress(newProgress);
mFakeTitleBar.setProgress(newProgress);
if (newProgress == 100) {
hideFakeTitleBar();
}
}
- } else if (!mInLoad) {
- // onPageFinished may have already been called but a subframe is
- // still loading and updating the progress. Reset mInLoad and update
- // the menu items.
- mInLoad = true;
- updateInLoadMenuItems();
+ } else {
+ if (!mInLoad) {
+ // onPageFinished may have already been called but a subframe is
+ // still loading and updating the progress. Reset mInLoad and
+ // update the menu items.
+ mInLoad = true;
+ updateInLoadMenuItems();
+ }
+ // When the page first begins to load, the Activity may still be
+ // paused, in which case showFakeTitleBar will do nothing. Call
+ // again as the page continues to load so that it will be shown.
+ // (Calling it will the fake title bar is already showing will also
+ // do nothing.
if (!mOptionsMenuOpen || mIconView) {
// This page has begun to load, so show the title bar
showFakeTitleBar();
onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
}
+ // This is to work around the fact that java.net.URI throws Exceptions
+ // instead of just encoding URL's properly
+ // Helper method for onDownloadStartNoStream
+ private static String encodePath(String path) {
+ char[] chars = path.toCharArray();
+
+ boolean needed = false;
+ for (char c : chars) {
+ if (c == '[' || c == ']') {
+ needed = true;
+ break;
+ }
+ }
+ if (needed == false) {
+ return path;
+ }
+
+ StringBuilder sb = new StringBuilder("");
+ for (char c : chars) {
+ if (c == '[' || c == ']') {
+ sb.append('%');
+ sb.append(Integer.toHexString(c));
+ } else {
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
/**
* Notify the host application a download should be done, even if there
* is a streaming viewer available for thise type.
return;
}
- // java.net.URI is a lot stricter than KURL so we have to undo
- // KURL's percent-encoding and redo the encoding using java.net.URI.
- URI uri = null;
+ // java.net.URI is a lot stricter than KURL so we have to encode some
+ // extra characters. Fix for b 2538060 and b 1634719
+ WebAddress webAddress;
try {
- // Undo the percent-encoding that KURL may have done.
- String newUrl = new String(URLUtil.decode(url.getBytes()));
- // Parse the url into pieces
- WebAddress w = new WebAddress(newUrl);
- String frag = null;
- String query = null;
- String path = w.mPath;
- // Break the path into path, query, and fragment
- if (path.length() > 0) {
- // Strip the fragment
- int idx = path.lastIndexOf('#');
- if (idx != -1) {
- frag = path.substring(idx + 1);
- path = path.substring(0, idx);
- }
- idx = path.lastIndexOf('?');
- if (idx != -1) {
- query = path.substring(idx + 1);
- path = path.substring(0, idx);
- }
- }
- uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
- query, frag);
+ webAddress = new WebAddress(url);
+ webAddress.mPath = encodePath(webAddress.mPath);
} catch (Exception e) {
- Log.e(LOGTAG, "Could not parse url for download: " + url, e);
+ // This only happens for very bad urls, we want to chatch the
+ // exception here
+ Log.e(LOGTAG, "Exception trying to parse url:" + url);
return;
}
String cookies = CookieManager.getInstance().getCookie(url);
ContentValues values = new ContentValues();
- values.put(Downloads.Impl.COLUMN_URI, uri.toString());
+ values.put(Downloads.Impl.COLUMN_URI, webAddress.toString());
values.put(Downloads.Impl.COLUMN_COOKIE_DATA, cookies);
values.put(Downloads.Impl.COLUMN_USER_AGENT, userAgent);
values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
values.put(Downloads.Impl.COLUMN_MIME_TYPE, mimetype);
values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, filename);
- values.put(Downloads.Impl.COLUMN_DESCRIPTION, uri.getHost());
+ values.put(Downloads.Impl.COLUMN_DESCRIPTION, webAddress.mHost);
if (contentLength > 0) {
values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, contentLength);
}
final Uri contentUri =
getContentResolver().insert(Downloads.Impl.CONTENT_URI, values);
}
-
+ Toast.makeText(this, R.string.download_pending, Toast.LENGTH_SHORT)
+ .show();
}
// -------------------------------------------------------------------------
public void setHttpAuthUsernamePassword(String host, String realm,
String username,
String password) {
- WebView w = mTabControl.getCurrentWebView();
+ WebView w = getTopWindow();
if (w != null) {
w.setHttpAuthUsernamePassword(host, realm, username, password);
}
private boolean mIsNetworkUp;
private boolean mDidStopLoad;
- private boolean mActivityInPause = true;
+ /* package */ boolean mActivityInPause = true;
private boolean mMenuIsDown;
/* hold a ref so we can auto-cancel if necessary */
private AlertDialog mAlertDialog;
- // Wait for credentials before loading google.com
- private ProgressDialog mCredsDlg;
-
// The up-to-date URL and title (these can be different from those stored
// in WebView, since it takes some time for the information in WebView to
// get updated)
private BroadcastReceiver mPackageInstallationReceiver;
+ private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins;
+
// activity requestCode
final static int COMBO_PAGE = 1;
final static int DOWNLOAD_PAGE = 2;
* A UrlData class to abstract how the content will be set to WebView.
* This base class uses loadUrl to show the content.
*/
- private static class UrlData {
+ /* package */ static class UrlData {
final String mUrl;
final Map<String, String> mHeaders;
final Intent mVoiceIntent;