diff options
29 files changed, 733 insertions, 206 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 826d1402..ccfebfc1 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -307,7 +307,8 @@ <provider android:name=".provider.MyNavigationProvider" android:authorities="com.android.swe.browser.mynavigation" - android:exported="false" /> + android:exported="false" + android:grantUriPermissions="true"/> <service android:name="org.chromium.content.app.SandboxedProcessService0" android:process=":sandboxed_process0" diff --git a/res/layout-sw600dp/title_bar_nav.xml b/res/layout-sw600dp/title_bar_nav.xml index a6b8a325..e6abe7c7 100644 --- a/res/layout-sw600dp/title_bar_nav.xml +++ b/res/layout-sw600dp/title_bar_nav.xml @@ -53,6 +53,13 @@ android:layout_weight="1.0" android:orientation="horizontal" android:background="@drawable/url_background"> + <com.android.browser.LocationButton + android:id="@+id/location_button" + android:layout_width="32dip" + android:layout_height="20dip" + android:visibility="gone" + style="@style/HoloButton" + android:layout_gravity="center" /> <ImageView android:id="@+id/url_icon" android:layout_width="32dip" diff --git a/res/layout/geolocation_permissions_prompt.xml b/res/layout/geolocation_permissions_prompt.xml index 1920c05c..d63ef848 100755 --- a/res/layout/geolocation_permissions_prompt.xml +++ b/res/layout/geolocation_permissions_prompt.xml @@ -68,14 +68,21 @@ android:id="@+id/dont_share_button" style="?android:attr/buttonBarButtonStyle" android:layout_weight="1" - android:layout_width="0dip" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/geolocation_permissions_prompt_dont_share" /> <Button + android:id="@+id/share_for_limited_time_button" + style="?android:attr/buttonBarButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/geolocation_permissions_prompt_share_for_limited_time"/> + <Button android:id="@+id/share_button" style="?android:attr/buttonBarButtonStyle" android:layout_weight="1" - android:layout_width="0dip" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/geolocation_permissions_prompt_share" /> </LinearLayout> diff --git a/res/layout/title_bar_nav.xml b/res/layout/title_bar_nav.xml index 2f1e0a27..3ee99fd0 100644 --- a/res/layout/title_bar_nav.xml +++ b/res/layout/title_bar_nav.xml @@ -42,6 +42,12 @@ android:paddingLeft="4dip" android:visibility="gone" android:src="@drawable/ic_incognito_holo_dark" /> + <com.android.browser.LocationButton + android:id="@+id/location_button" + android:layout_width="32dip" + android:layout_height="32dip" + android:visibility="gone" + style="@style/HoloButton"/> <FrameLayout android:id="@+id/iconcombo" android:layout_width="44dip" @@ -116,7 +122,7 @@ android:src="@drawable/ic_windows_holo_dark" style="@style/HoloButton" /> <ImageButton - android:id="@+id/more" + android:id="@+id/more_browser_settings" android:layout_width="wrap_content" android:layout_height="match_parent" style="@style/HoloButton" diff --git a/res/values/all_search_engines.xml b/res/values/all_search_engines.xml index 2c199f34..5a1de969 100644 --- a/res/values/all_search_engines.xml +++ b/res/values/all_search_engines.xml @@ -36,6 +36,14 @@ http://www.opensearch.org/Specifications/OpenSearch/1.1/Draft_4#OpenSearch_1.1_p --> <resources> + <string-array name="DuckDuckGo" translatable="false"> + <item>DuckDuckGo</item> + <item>duckduckgo.com</item> + <item>http://duckduckgo.com/favicon.ico</item> + <item>https://duckduckgo.com/?q={searchTerms}</item> + <item>UTF-8</item> + <item></item> + </string-array> <string-array name="yahoo_uk" translatable="false"> <item>Yahoo! UK & Ireland</item> <item>uk.yahoo.com</item> @@ -151,9 +159,9 @@ http://www.opensearch.org/Specifications/OpenSearch/1.1/Draft_4#OpenSearch_1.1_p <string-array name="yahoo_cn" translatable="false"> <item>中国雅虎</item> <item>cn.yahoo.com</item> - <item>http://search.cn.yahoo.com/favicon.ico</item> - <item>http://search.cn.yahoo.com/search?ei={inputEncoding}&fr=crmas&p={searchTerms}</item> - <item>GB2312</item> + <item>http://search.yahoo.com/favicon.ico</item> + <item>http://search.yahoo.com/search?ei={inputEncoding}&fr=crmas&p={searchTerms}</item> + <item>UTF-8</item> <item></item> </string-array> <string-array name="onet" translatable="false"> diff --git a/res/values/donottranslate-search_engines.xml b/res/values/donottranslate-search_engines.xml index f83526b7..1709dc80 100644 --- a/res/values/donottranslate-search_engines.xml +++ b/res/values/donottranslate-search_engines.xml @@ -26,6 +26,7 @@ Each value in the string-array is the name of a value in all_search_engines.xml <item>baidu</item> <item>yahoo</item> <item>bing</item> + <item>DuckDuckGo</item> </string-array> <string name="default_search_engine_value">baidu</string> </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index eae9ac69..0d505832 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -492,6 +492,10 @@ <string name="pref_security_show_security_warning">Show security warnings</string> <!-- Settings summmary --> <string name="pref_security_show_security_warning_summary">Show warning if there\'s a problem with a site\'s security</string> + <!-- Settings Label --> + <string name="pref_do_not_track">Do Not Track</string> + <!-- Settings summary --> + <string name="pref_do_not_track_summary">Send a Do Not Track request with your browsing traffic</string> <!-- Settings label --> <string name="pref_security_accept_cookies">Accept cookies</string> <!-- Settings summary --> @@ -802,6 +806,7 @@ <!-- Intentional empty elements - used for overlay feature --> <string name="def_wifi_browser_interaction_remind_type"></string> <string name="def_action_wifi_selection_data_connections"></string> + <string name="def_intent_pick_network"></string> <string name="def_landing_page"></string> <string name="def_useragent"></string> @@ -867,20 +872,28 @@ <!-- Geolocation --> <!-- Permissions prompt --> <string name="geolocation_permissions_prompt_message"><xliff:g id="website origin" example="maps.google.com">%s</xliff:g> wants to know your location</string> - <string name="geolocation_permissions_prompt_share">Share location</string> - <string name="geolocation_permissions_prompt_dont_share">Decline</string> + <string name="geolocation_permissions_prompt_share">Allow</string> + <string name="geolocation_permissions_prompt_share_for_limited_time">Allow for 24 hours</string> + <string name="geolocation_permissions_prompt_dont_share">Deny</string> <string name="geolocation_permissions_prompt_remember">Remember preference</string> <!-- Permissions prompt toast --> <string name="geolocation_permissions_prompt_toast_allowed">This site can access your location. Change this on the Settings > Advanced > Website screen.</string> <string name="geolocation_permissions_prompt_toast_disallowed">This site cannot access your location. Change this on the Settings > Advanced > Website screen.</string> <!-- Settings -> Advanced -> Website settings -> <origin> --> - <string name="geolocation_settings_page_title">Clear location access</string> + <string name="geolocation_settings_page_title">Edit location access</string> <string name="geolocation_settings_page_summary_allowed">This site can currently access your location</string> <string name="geolocation_settings_page_summary_not_allowed">This site can\'t currently access your location</string> <!-- Settings page dialog --> - <string name="geolocation_settings_page_dialog_message">Clear location access for this website?</string> + <string name="geolocation_settings_page_dialog_title">Edit location policy for\n<xliff:g id="website origin" example="maps.google.com">%s</xliff:g></string> + <string-array name="geolocation_settings_choices"> + <item>Deny forever</item> + <item>Allow for 24 hours</item> + <item>Allow forever</item> + <item>Always ask</item> + </string-array> <string name="geolocation_settings_page_dialog_ok_button">OK</string> <string name="geolocation_settings_page_dialog_cancel_button">Cancel</string> + <!-- Label for the menu item in the website settings activity used to clear data stored by all websites --> <string name="website_settings_clear_all">Clear all</string> <string name="website_settings_clear_all_dialog_message">Delete all website data and location permissions?</string> diff --git a/res/xml/privacy_security_preferences.xml b/res/xml/privacy_security_preferences.xml index 26336000..4c14c866 100644 --- a/res/xml/privacy_security_preferences.xml +++ b/res/xml/privacy_security_preferences.xml @@ -37,6 +37,12 @@ android:title="@string/pref_security_show_security_warning" android:summary="@string/pref_security_show_security_warning_summary" /> + <CheckBoxPreference + android:key="do_not_track" + android:defaultValue="true" + android:title="@string/pref_do_not_track" + android:summary="@string/pref_do_not_track_summary" /> + <PreferenceCategory android:title="@string/pref_privacy_cookies_title"> <CheckBoxPreference android:key="accept_cookies" @@ -94,7 +100,7 @@ android:key="privacy_clear_passwords" android:title="@string/pref_privacy_clear_passwords" android:summary="@string/pref_privacy_clear_passwords_summary" - android:dialogMessage="@string/pref_privacy_clear_passwords_dlg" + android:dialogMessage="@string/pref_privacy_clear_passwords_dlg" android:dialogIcon="@android:drawable/ic_dialog_alert"/> </PreferenceCategory> diff --git a/src/com/android/browser/AutoFillSettingsFragment.java b/src/com/android/browser/AutoFillSettingsFragment.java index e87cb892..cc771028 100644 --- a/src/com/android/browser/AutoFillSettingsFragment.java +++ b/src/com/android/browser/AutoFillSettingsFragment.java @@ -56,6 +56,7 @@ public class AutoFillSettingsFragment extends Fragment { private EditText mPhoneEdit; private MenuItem mSaveMenuItem; + private MenuItem mDeleteMenuItem; private boolean mInitialised; @@ -92,6 +93,7 @@ public class AutoFillSettingsFragment extends Fragment { } updateSaveMenuItemState(); + updateDeleteMenuItemState(); } public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -104,6 +106,7 @@ public class AutoFillSettingsFragment extends Fragment { private class FieldChangedListener implements TextWatcher { public void afterTextChanged(Editable s) { updateSaveMenuItemState(); + updateDeleteMenuItemState(); } public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -152,7 +155,9 @@ public class AutoFillSettingsFragment extends Fragment { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.autofill_profile_editor, menu); mSaveMenuItem = menu.findItem(R.id.autofill_profile_editor_save_profile_menu_id); + mDeleteMenuItem = menu.findItem(R.id.autofill_profile_editor_delete_profile_menu_id); updateSaveMenuItemState(); + updateDeleteMenuItemState(); } @Override @@ -176,6 +181,7 @@ public class AutoFillSettingsFragment extends Fragment { mSettings.updateAutoFillProfile(null); updateSaveMenuItemState(); + updateDeleteMenuItemState(); return true; case R.id.autofill_profile_editor_save_profile_menu_id: @@ -248,10 +254,38 @@ public class AutoFillSettingsFragment extends Fragment { mInitialised = true; updateSaveMenuItemState(); + updateDeleteMenuItemState(); return v; } + private void updateDeleteMenuItemState() { + if (mDeleteMenuItem == null) { + return; + } + + if (!mInitialised) { + mDeleteMenuItem.setEnabled(false); + return; + } + + boolean currentState = mDeleteMenuItem.isEnabled(); + boolean newState = (mFullNameEdit.getText().toString().length() > 0 || + mEmailEdit.getText().toString().length() > 0 || + mCompanyEdit.getText().toString().length() > 0 || + mAddressLine1Edit.getText().toString().length() > 0 || + mAddressLine2Edit.getText().toString().length() > 0 || + mCityEdit.getText().toString().length() > 0 || + mStateEdit.getText().toString().length() > 0 || + mZipEdit.getText().toString().length() > 0 || + mCountryEdit.getText().toString().length() > 0) && + mPhoneEdit.getError() == null; + + if (currentState != newState) { + mDeleteMenuItem.setEnabled(newState); + } + } + private void updateSaveMenuItemState() { if (mSaveMenuItem == null) { return; diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java index 7c166aa0..27b2b829 100644 --- a/src/com/android/browser/BaseUi.java +++ b/src/com/android/browser/BaseUi.java @@ -313,11 +313,10 @@ public abstract class BaseUi implements UI { Runnable mRunnable = null; protected void scheduleRemoveTab(Tab tabToRemove, Tab tabToWaitFor) { - android.os.Handler handler = mTitleBar.getHandler(); //remove previously scehduled tab if (mTabToRemove != null) { if (mRunnable != null) - handler.removeCallbacks(mRunnable); + mTitleBar.removeCallbacks(mRunnable); removeTabFromContentView(mTabToRemove); mTabToRemove.performPostponedDestroy(); mRunnable = null; @@ -334,11 +333,10 @@ public abstract class BaseUi implements UI { protected void tryRemoveTab() { mNumRemoveTries++; - android.os.Handler handler = mTitleBar.getHandler(); // Ensure the webview is still valid if (mNumRemoveTries < 20 && mTabToWaitFor.getWebView() != null) { if (!mTabToWaitFor.getWebView().isReady()) { - if (mRunnable != null) { + if (mRunnable == null) { mRunnable = new Runnable() { public void run() { tryRemoveTab(); @@ -348,13 +346,13 @@ public abstract class BaseUi implements UI { /*if the new tab is still not ready, wait another 2 frames before trying again. 1 frame for the tab to render the first frame, another 1 frame to make sure the swap is done*/ - handler.postDelayed(mRunnable, 33); + mTitleBar.postDelayed(mRunnable, 33); return; } } if (mTabToRemove != null) { if (mRunnable != null) - handler.removeCallbacks(mRunnable); + mTitleBar.removeCallbacks(mRunnable); removeTabFromContentView(mTabToRemove); mTabToRemove.performPostponedDestroy(); mRunnable = null; diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java index 25dcd022..1e2e84aa 100644 --- a/src/com/android/browser/BrowserSettings.java +++ b/src/com/android/browser/BrowserSettings.java @@ -957,6 +957,10 @@ public class BrowserSettings implements OnSharedPreferenceChangeListener, return mPrefs.getBoolean(PREF_SHOW_SECURITY_WARNINGS, true); } + public boolean doNotTrack() { + return mPrefs.getBoolean(PREF_DO_NOT_TRACK, true); + } + public boolean acceptCookies() { return mPrefs.getBoolean(PREF_ACCEPT_COOKIES, true); } diff --git a/src/com/android/browser/BrowserYesNoPreference.java b/src/com/android/browser/BrowserYesNoPreference.java index 4bb3c9cf..e8d6af9a 100644 --- a/src/com/android/browser/BrowserYesNoPreference.java +++ b/src/com/android/browser/BrowserYesNoPreference.java @@ -33,10 +33,9 @@ class BrowserYesNoPreference extends DialogPreference { super.onDialogClosed(positiveResult); if (callChangeListener(positiveResult)) { - setEnabled(false); if (!positiveResult) return; - + setEnabled(false); BrowserSettings settings = BrowserSettings.getInstance(); if (PreferenceKeys.PREF_PRIVACY_CLEAR_CACHE.equals(getKey())) { settings.clearCache(); diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java index 012191e9..8bd85f69 100644 --- a/src/com/android/browser/Controller.java +++ b/src/com/android/browser/Controller.java @@ -195,10 +195,6 @@ public class Controller private boolean mShouldShowErrorConsole; private boolean mNetworkShouldNotify = true; - private boolean mJsInterfaceEnabled = false; - - private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins; - // FIXME, temp address onPrepareMenu performance problem. // When we move everything out of view, we should rewrite this. private int mCurrentMenuState = 0; @@ -276,11 +272,6 @@ public class Controller BrowserContract.Bookmarks.CONTENT_URI, true, mBookmarksObserver); mNetworkHandler = new NetworkStateHandler(mActivity, this); - // Start watching the default geolocation permissions - mSystemAllowGeolocationOrigins = - new SystemAllowGeolocationOrigins(mActivity.getApplicationContext()); - mSystemAllowGeolocationOrigins.start(); - openIconDatabase(); } @@ -343,6 +334,7 @@ public class Controller } } else { t = openTab(urlData); + t.setDerivedFromIntent(true); } if (t != null) { t.setAppId(intent.getStringExtra(Browser.EXTRA_APPLICATION_ID)); @@ -363,6 +355,9 @@ public class Controller ArrayList<Long> restoredTabs = new ArrayList<Long>(tabs.size()); for (Tab t : tabs) { restoredTabs.add(t.getId()); + if (t != mTabControl.getCurrentTab()) { + t.pause(); + } } BackgroundHandler.execute(new PruneThumbnails(mActivity, restoredTabs)); if (tabs.size() == 0) { @@ -818,9 +813,6 @@ public class Controller // Destroy all the tabs mTabControl.destroy(); WebIconDatabase.getInstance().close(); - // Stop watching the default geolocation permissions - mSystemAllowGeolocationOrigins.stop(); - mSystemAllowGeolocationOrigins = null; } protected boolean isActivityPaused() { @@ -871,45 +863,42 @@ public class Controller R.string.def_wifi_browser_interaction_remind_type); final String selectionConnnection = getContext().getResources().getString( R.string.def_action_wifi_selection_data_connections); + final String wifiSelection = getContext().getResources().getString( + R.string.def_intent_pick_network); - if (reminderType.isEmpty() || selectionConnnection.isEmpty()) + if (reminderType.isEmpty() || selectionConnnection.isEmpty() || + wifiSelection.isEmpty()) return; ConnectivityManager conMgr = (ConnectivityManager) this.getContext().getSystemService( Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = conMgr.getActiveNetworkInfo(); + WifiManager wifiMgr = (WifiManager) this.getContext() + .getSystemService(Context.WIFI_SERVICE); if (networkInfo == null || (networkInfo != null && (networkInfo.getType() != ConnectivityManager.TYPE_WIFI))) { int isReminder = Settings.System.getInt(mActivity.getContentResolver(), reminderType, NETWORK_SWITCH_TYPE_OK); - if (isReminder == NETWORK_SWITCH_TYPE_OK) { - mNetworkShouldNotify = false; + List<ScanResult> list = wifiMgr.getScanResults(); + // Have no AP's for Wifi's fall back to data + if (list != null && list.size() == 0 && isReminder == NETWORK_SWITCH_TYPE_OK) { Intent intent = new Intent(selectionConnnection); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); this.getContext().startActivity(intent); } else { - if (!mNetworkHandler.isNetworkUp()) - view.setNetworkAvailable(false); - } - } - } - - /*** - * Add/remove a Javascript interface for a local default homepage only - */ - private void handleJsInterface(WebView webview){ - if (webview.getUrl() != null && - webview.getUrl().equals(mActivity.getResources().getString(R.string.homepage_base)) && - webview.getUrl().startsWith("file:///")) { - mJsInterfaceEnabled = true; - webview.getSettings().setJavaScriptEnabled(true); - webview.addJavascriptInterface(mActivity, "default_homepage"); - } else { - if (mJsInterfaceEnabled) { - webview.removeJavascriptInterface("default_homepage"); - mJsInterfaceEnabled = false; + // Request to select Wifi AP + try { + Intent intent = new Intent(wifiSelection); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + this.getContext().startActivity(intent); + } catch (Exception e) { + String err_msg = this.getContext().getString( + R.string.acivity_not_found, wifiSelection); + Toast.makeText(this.getContext(), err_msg, Toast.LENGTH_LONG).show(); + } } + mNetworkShouldNotify = false; } } @@ -927,10 +916,10 @@ public class Controller // reset sync timer to avoid sync starts during loading a page CookieSyncManager.getInstance().resetSync(); WifiManager wifiMgr = (WifiManager) this.getContext() - .getSystemService(Context.WIFI_SERVICE); + .getSystemService(Context.WIFI_SERVICE); boolean networkNotifier = mActivity.getApplicationContext().getResources().getBoolean(R.bool.network_notifier); - if (networkNotifier && mNetworkShouldNotify && wifiMgr.isWifiEnabled()) { + if (networkNotifier && mNetworkShouldNotify && wifiMgr.isWifiEnabled()){ handleNetworkNotify(view); } else { if (!mNetworkHandler.isNetworkUp()) { @@ -981,7 +970,6 @@ public class Controller int newProgress = tab.getLoadProgress(); if (newProgress == 100) { - handleJsInterface(tab.getWebView()); CookieSyncManager.getInstance().sync(); // onProgressChanged() may continue to be called after the main // frame has finished loading, as any remaining sub frames continue @@ -1188,7 +1176,10 @@ public class Controller // file. Remove it. if (tab == mTabControl.getCurrentTab()) { // In this case, the Tab is still on top. - goBackOnePageOrQuit(); + if (tab.getDerivedFromIntent()) + closeTab(tab); + else + goBackOnePageOrQuit(); } else { // In this case, it is not. closeTab(tab); @@ -2709,6 +2700,8 @@ public class Controller // the tab is guaranteed to have a webview after setCurrentTab mUi.setActiveTab(tab); tab.setTimeStamp(); + //Purge active tabs + MemoryMonitor.purgeActiveTabs(mActivity.getApplicationContext(), this, mSettings); } } @@ -2854,23 +2847,13 @@ public class Controller private Tab createNewTab(boolean incognito, boolean setActive, boolean useCurrent) { Tab tab = null; - MemoryMonitor memMonitor = null; if (mTabControl.canCreateNewTab()) { - if (mSettings.enableMemoryMonitor()) { - Log.d(LOGTAG, " Memory Monitor Enabled ."); - memMonitor = MemoryMonitor.getInstance(mActivity.getApplicationContext(),this); - if (memMonitor != null) { - //Remove webview associated with the oldest tab - memMonitor.destroyLeastRecentlyActiveTab(); - } - } else { - Log.d(LOGTAG, " Memory Monitor disabled ."); - } tab = mTabControl.createNewTab(incognito); addTab(tab); - tab.setTimeStamp(); if (setActive) { setActiveTab(tab); + } else { + tab.pause(); } } else { if (useCurrent) { diff --git a/src/com/android/browser/DownloadHandler.java b/src/com/android/browser/DownloadHandler.java index 88e66798..f42ee768 100644 --- a/src/com/android/browser/DownloadHandler.java +++ b/src/com/android/browser/DownloadHandler.java @@ -41,6 +41,9 @@ import com.android.browser.R; import com.android.browser.platformsupport.WebAddress; import com.android.browser.reflect.ReflectHelper; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import java.io.File; /** * Handle download requests @@ -184,7 +187,8 @@ public class DownloadHandler { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse(url), mimetype); try { - String title = URLUtil.guessFileName(url, contentDisposition, mimetype); + String trimmedcontentDisposition = trimContentDisposition(contentDisposition); + String title = URLUtil.guessFileName(url, trimmedcontentDisposition, mimetype); intent.putExtra(Intent.EXTRA_TITLE, title); activity.startActivity(intent); } catch (ActivityNotFoundException ex) { @@ -278,6 +282,9 @@ public class DownloadHandler { String mimetype, String referer, boolean privateBrowsing, long contentLength) { initStorageDefaultPath(activity); + + contentDisposition = trimContentDisposition(contentDisposition); + String filename = URLUtil.guessFileName(url, contentDisposition, mimetype); @@ -317,6 +324,27 @@ public class DownloadHandler { } + static String trimContentDisposition(String contentDisposition) { + final Pattern CONTENT_DISPOSITION_PATTERN = + Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*;", + Pattern.CASE_INSENSITIVE); + + if (contentDisposition != null) { + + try { + Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); + if (m.find()) { + return m.group(); + } else { + return contentDisposition; + } + } catch (IllegalStateException ex) { + // This function is defined as returning null when it can't parse the header + } + } + return null; + } + public static void initStorageDefaultPath(Context context) { mExternalStorage = getExternalStorageDirectory(context); if (isPhoneStorageSupported()) { diff --git a/src/com/android/browser/DownloadSettings.java b/src/com/android/browser/DownloadSettings.java index cca26ccb..4c7c829b 100644 --- a/src/com/android/browser/DownloadSettings.java +++ b/src/com/android/browser/DownloadSettings.java @@ -126,7 +126,9 @@ public class DownloadSettings extends Activity { String filenameExtension = DownloadHandler.getFilenameExtension(filename); - if (mimetype == null || mimetype.isEmpty()) { + // introspect for octet stream mimetype what type of file extension it has + // and reassign mimetype + if (mimetype == null || mimetype.isEmpty() || mimetype.equals(OCTET_STREAM)) { String updatedFileName = filenameBase + "." + filenameExtension; Object[] params = {updatedFileName}; diff --git a/src/com/android/browser/GeolocationPermissionsPrompt.java b/src/com/android/browser/GeolocationPermissionsPrompt.java index 127107fd..d0e94ad2 100755 --- a/src/com/android/browser/GeolocationPermissionsPrompt.java +++ b/src/com/android/browser/GeolocationPermissionsPrompt.java @@ -18,14 +18,17 @@ package com.android.browser; import com.android.browser.R; +import org.codeaurora.swe.GeolocationPermissions; +import org.json.JSONArray; + import android.content.Context; import android.net.Uri; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; -import android.webkit.GeolocationPermissions; import android.widget.Button; import android.widget.CheckBox; +import android.widget.CompoundButton; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; @@ -33,11 +36,14 @@ import android.widget.Toast; public class GeolocationPermissionsPrompt extends RelativeLayout { private TextView mMessage; private Button mShareButton; + private Button mShareForLimitedTimeButton; private Button mDontShareButton; private CheckBox mRemember; - private GeolocationPermissions.Callback mCallback; + private android.webkit.GeolocationPermissions.Callback mCallback; private String mOrigin; + private static final long MILLIS_PER_DAY = 86400000; + public GeolocationPermissionsPrompt(Context context) { this(context, null); } @@ -55,17 +61,30 @@ public class GeolocationPermissionsPrompt extends RelativeLayout { private void init() { mMessage = (TextView) findViewById(R.id.message); mShareButton = (Button) findViewById(R.id.share_button); + mShareForLimitedTimeButton = (Button) + findViewById(R.id.share_for_limited_time_button); mDontShareButton = (Button) findViewById(R.id.dont_share_button); mRemember = (CheckBox) findViewById(R.id.remember); + mRemember.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mShareForLimitedTimeButton.setEnabled(isChecked); + } + }); + mShareButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - handleButtonClick(true); + handleButtonClick(true, GeolocationPermissions.DO_NOT_EXPIRE); + } + }); + mShareForLimitedTimeButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + handleButtonClick(true, System.currentTimeMillis() + MILLIS_PER_DAY); } }); mDontShareButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - handleButtonClick(false); + handleButtonClick(false, GeolocationPermissions.DO_NOT_EXPIRE); } }); } @@ -74,7 +93,8 @@ public class GeolocationPermissionsPrompt extends RelativeLayout { * Shows the prompt for the given origin. When the user clicks on one of * the buttons, the supplied callback is be called. */ - public void show(String origin, GeolocationPermissions.Callback callback) { + public void show(String origin, + android.webkit.GeolocationPermissions.Callback callback) { mOrigin = origin; mCallback = callback; Uri uri = Uri.parse(mOrigin); @@ -94,7 +114,7 @@ public class GeolocationPermissionsPrompt extends RelativeLayout { /** * Handles a click on one the buttons by invoking the callback. */ - private void handleButtonClick(boolean allow) { + private void handleButtonClick(boolean allow, long expirationTime) { hide(); boolean remember = mRemember.isChecked(); @@ -108,7 +128,11 @@ public class GeolocationPermissionsPrompt extends RelativeLayout { toast.show(); } - mCallback.invoke(mOrigin, allow, remember); + // Encode the expirationTime and origin as a JSON string. + JSONArray jsonArray = new JSONArray(); + jsonArray.put(expirationTime); + jsonArray.put(mOrigin); + mCallback.invoke(jsonArray.toString(), allow, remember); } /** diff --git a/src/com/android/browser/IntentHandler.java b/src/com/android/browser/IntentHandler.java index ec192461..f829ae1f 100644 --- a/src/com/android/browser/IntentHandler.java +++ b/src/com/android/browser/IntentHandler.java @@ -109,6 +109,7 @@ public class IntentHandler { if (intent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false) || urlData.isPreloaded()) { Tab t = mController.openTab(urlData); + t.setDerivedFromIntent(true); return; } /* @@ -123,7 +124,8 @@ public class IntentHandler { if (!TextUtils.isEmpty(urlData.mUrl) && urlData.mUrl.startsWith("javascript:")) { // Always open javascript: URIs in new tabs - mController.openTab(urlData); + Tab jsTab = mController.openTab(urlData); + jsTab.setDerivedFromIntent(true); return; } if (Intent.ACTION_VIEW.equals(action) @@ -165,6 +167,7 @@ public class IntentHandler { Tab tab = mController.openTab(urlData); if (tab != null) { tab.setAppId(appId); + tab.setDerivedFromIntent(true); if ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { tab.setCloseOnBack(true); } diff --git a/src/com/android/browser/LocationButton.java b/src/com/android/browser/LocationButton.java new file mode 100644 index 00000000..e805e438 --- /dev/null +++ b/src/com/android/browser/LocationButton.java @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package com.android.browser; + +import org.codeaurora.swe.GeolocationPermissions; +import org.codeaurora.swe.GeolocationPermissions.OnGeolocationPolicyModifiedListener; +import org.json.JSONArray; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.net.Uri; +import android.util.AttributeSet; +import android.view.View; +import android.webkit.ValueCallback; +import android.widget.ImageButton; + +public class LocationButton extends ImageButton + implements OnGeolocationPolicyModifiedListener { + private GeolocationPermissions mGeolocationPermissions; + private long mCurrentTabId; + private String mCurrentOrigin; + private boolean mCurrentIncognito; + + private static final long MILLIS_PER_DAY = 86400000; + + protected long geolocationPolicyExpiration; + protected boolean geolocationPolicyOriginAllowed; + + public LocationButton(Context context) { + super(context); + } + + public LocationButton(Context context, AttributeSet attrs) { + super(context, attrs); + + } + + public LocationButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + init(); + } + + private void init() { + mGeolocationPermissions = GeolocationPermissions.getInstance(); + mGeolocationPermissions.registerOnGeolocationPolicyModifiedListener(this); + mCurrentTabId = -1; + mCurrentOrigin = null; + mCurrentIncognito = false; + + setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!mCurrentOrigin.isEmpty()) { + final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + final GeolocationPermissions geolocationPermissions = + (mCurrentIncognito ? + GeolocationPermissions.getIncognitoInstance() : + GeolocationPermissions.getInstance()); + + DialogInterface.OnClickListener alertDialogListener = + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dlg, int which) { + String origin = mCurrentOrigin; + int selectedPosition = ((AlertDialog)dlg) + .getListView().getCheckedItemPosition(); + switch (selectedPosition) { + case 0: // Deny forever + geolocationPermissions.deny(origin); + break; + case 1: // Extend for 24 hours + // encode the expiration time and origin as a JSON string + JSONArray jsonArray = new JSONArray(); + jsonArray.put(System.currentTimeMillis() + MILLIS_PER_DAY); + jsonArray.put(origin); + geolocationPermissions.allow(jsonArray.toString()); + break; + case 2: // Allow forever + geolocationPermissions.allow(origin); + break; + case 3: // Always ask + geolocationPermissions.clear(origin); + break; + default: + break; + } + }}; + + builder.setTitle(String.format(getResources() + .getString(R.string.geolocation_settings_page_dialog_title), + "http".equals(Uri.parse(mCurrentOrigin).getScheme()) ? + mCurrentOrigin.substring(7) : mCurrentOrigin)) + .setPositiveButton(R.string.geolocation_settings_page_dialog_ok_button, + alertDialogListener) + .setNegativeButton(R.string.geolocation_settings_page_dialog_cancel_button, null); + + final ValueCallback<Long> getExpirationCallback = new ValueCallback<Long>() { + public void onReceiveValue(Long expirationTime) { + if (expirationTime != null) { + geolocationPolicyExpiration = expirationTime.longValue(); + // Set radio button and location icon + if (!geolocationPolicyOriginAllowed) { + // 0: Deny forever + builder.setSingleChoiceItems(R.array.geolocation_settings_choices, 0, null); + } else { + if (geolocationPolicyExpiration + != GeolocationPermissions.DO_NOT_EXPIRE) { + // 1: Allow for 24 hours + builder.setSingleChoiceItems(R.array.geolocation_settings_choices, 1, null); + } else { + // 2: Allow forever + builder.setSingleChoiceItems(R.array.geolocation_settings_choices, 2, null); + } + } + } + builder.show(); + }}; + + final ValueCallback<Boolean> getAllowedCallback = new ValueCallback<Boolean>() { + public void onReceiveValue(Boolean allowed) { + if (allowed != null) { + geolocationPolicyOriginAllowed = allowed.booleanValue(); + //Get the policy expiration time + geolocationPermissions + .getExpirationTime(mCurrentOrigin, getExpirationCallback); + } + }}; + + geolocationPermissions.hasOrigin(mCurrentOrigin, + new ValueCallback<Boolean>() { + public void onReceiveValue(Boolean hasOrigin) { + if (hasOrigin != null && hasOrigin.booleanValue()) { + //Get whether origin is allowed or denied + geolocationPermissions.getAllowed(mCurrentOrigin, + getAllowedCallback); + } + } + }); + } + } + }); + } + + public void onTabDataChanged(Tab tab) { + long tabId = tab.getId(); + String origin = GeolocationPermissions.getOriginFromUrl(tab.getUrl()); + boolean incognito = tab.isPrivateBrowsingEnabled(); + + if (mCurrentTabId != tabId) { + mCurrentTabId = tabId; + mCurrentOrigin = origin; + + // Switch GeolocationPermissions if we went from a regular to an + // incognito tab or vice versa + if (mCurrentIncognito != incognito) { + mCurrentIncognito = incognito; + mGeolocationPermissions = mCurrentIncognito ? + GeolocationPermissions.getIncognitoInstance() : + GeolocationPermissions.getInstance(); + mGeolocationPermissions.registerOnGeolocationPolicyModifiedListener(this); + } + update(); + } + // Update icon if we are in the same tab and origin has changed + else if (!((mCurrentOrigin == null && origin == null) || + (mCurrentOrigin != null && origin != null + && mCurrentOrigin.equals(origin)))) { + mCurrentOrigin = origin; + update(); + } + } + + public void update() { + if (mCurrentOrigin != null) { + mGeolocationPermissions.hasOrigin(mCurrentOrigin, + new ValueCallback<Boolean>() { + public void onReceiveValue(Boolean hasOrigin) { + if (hasOrigin != null && hasOrigin.booleanValue()) { + mGeolocationPermissions.getAllowed(mCurrentOrigin, + new ValueCallback<Boolean>() { + public void onReceiveValue(Boolean allowed) { + if (allowed != null) { + if (allowed.booleanValue()) { + LocationButton.this.setImageResource(R.drawable.ic_gps_on_holo_dark); + LocationButton.this.setVisibility(VISIBLE); + } else { + LocationButton.this.setImageResource(R.drawable.ic_gps_denied_holo_dark); + LocationButton.this.setVisibility(VISIBLE); + } + } + } + }); + } else { + LocationButton.this.setVisibility(GONE); + } + } + }); + } else { + this.setVisibility(GONE); + } + } + + @Override + public void onGeolocationPolicyAdded(String origin, boolean allow) { + if (mCurrentOrigin != null && mCurrentOrigin.equals(origin)) { + this.setImageResource(allow ? R.drawable.ic_gps_on_holo_dark : + R.drawable.ic_gps_denied_holo_dark); + this.setVisibility(VISIBLE); + } + } + + @Override + public void onGeolocationPolicyCleared(String origin) { + if (mCurrentOrigin != null && mCurrentOrigin.equals(origin)) { + this.setVisibility(GONE); + } + } + + @Override + public void onGeolocationPolicyClearedAll() { + this.setVisibility(GONE); + } + +} diff --git a/src/com/android/browser/MemoryMonitor.java b/src/com/android/browser/MemoryMonitor.java index a18f698a..36c714fe 100644 --- a/src/com/android/browser/MemoryMonitor.java +++ b/src/com/android/browser/MemoryMonitor.java @@ -32,85 +32,48 @@ package com.android.browser; import android.app.ActivityManager; import android.content.Context; -import android.util.Log; import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; public class MemoryMonitor { - //This number is used with device memory class to calculate max number - //of active tabs. - private static int sMaxActiveTabs = 0; - private static MemoryMonitor sMemoryMonitor; - private TabControl mTabControl; - private final static String LOGTAG = "MemoryMonitor"; - - // Should be called only once - - public static MemoryMonitor getInstance(Context context, - Controller controller) { - if (sMemoryMonitor == null) { - sMemoryMonitor = new MemoryMonitor(context,controller); - } - return sMemoryMonitor; - } - - MemoryMonitor(Context context,Controller controller) { - mTabControl = controller.getTabControl(); - sMaxActiveTabs = getMaxActiveTabs(context); - Log.d(LOGTAG,"Max Active Tabs: "+ sMaxActiveTabs); - } - - private int getActiveTabs() { - int numNativeActiveTab = 0; - int size = mTabControl.getTabCount(); - - for (int i = 0; i < size; i++) { - Tab tab = mTabControl.getTab(i); - if (((Tab)tab).isNativeActive()){ - numNativeActiveTab++; - } - } - return numNativeActiveTab; - } - /** * if number of tabs whose native tab is active, is greater * than MAX_ACTIVE_TABS destroy the nativetab of oldest used Tab */ + public static void purgeActiveTabs(Context context, + Controller controller, + BrowserSettings settings) { + if(!settings.enableMemoryMonitor()) + return; - public void destroyLeastRecentlyActiveTab() { - int numActiveTabs = getActiveTabs(); - int numActiveTabsToRelease = numActiveTabs - sMaxActiveTabs; + int maxActiveTabs = getMaxActiveTabs(context); + TabControl tabControl = controller.getTabControl(); - // The most common case will be that we need to delete one - // NativeTab to make room for a new one. So, find the most-stale. - if (numActiveTabsToRelease == 1) { - Tab mostStaleTab = null; - for (Tab t : mTabControl.getTabs()) { - if (t.isNativeActive() && !(t.inForeground())) { - if (mostStaleTab == null){ - mostStaleTab = t; - } - else { - if (t.getTimestamp().compareTo(mostStaleTab. - getTimestamp()) < 0) { - mostStaleTab = t; - } - } - } - } - if (mostStaleTab != null) { - mostStaleTab.destroy(); - } - } else if (numActiveTabsToRelease > 1) { - // Since there is more than 1 "extra" tab, just release all - // NativeTabs in the background. This would be true when - // tracking was turned on after multiple tabs already exists - for (Tab t : mTabControl.getTabs()) { - if (t.isNativeActive() && !(t.inForeground())) { - t.destroy(); - } + ArrayList<Tab> activeTabList = new ArrayList<Tab>(); + + for (int i = 0; i < tabControl.getTabCount(); i++) { + Tab tab = tabControl.getTab(i); + if(tab.isNativeActive()) + activeTabList.add(tab); + } + + int numActiveTabsToRelease = activeTabList.size() - maxActiveTabs; + + if(numActiveTabsToRelease < 1) + return; + // sort tabs in order of LRU first + Collections.sort(activeTabList, new Comparator<Tab>() { + @Override + public int compare(Tab tab1, Tab tab2) { + return tab1.getTimestamp().compareTo(tab2.getTimestamp()); } + }); + + for(int i = 0; i < numActiveTabsToRelease; i++) { + activeTabList.get(i).destroy(); } } @@ -118,7 +81,7 @@ public class MemoryMonitor { * Returns the default max number of active tabs based on device's * memory class. */ - static int getMaxActiveTabs(Context context) { + private static int getMaxActiveTabs(Context context) { // We use device memory class to decide number of active tabs // (minimum memory class is 16). ActivityManager am =(ActivityManager)context. diff --git a/src/com/android/browser/NavigationBarBase.java b/src/com/android/browser/NavigationBarBase.java index da3f3da2..100e8d7e 100644 --- a/src/com/android/browser/NavigationBarBase.java +++ b/src/com/android/browser/NavigationBarBase.java @@ -53,10 +53,12 @@ public class NavigationBarBase extends LinearLayout implements protected TitleBar mTitleBar; protected UiController mUiController; protected UrlInputView mUrlInput; + protected LocationButton mLocationButton; private ImageView mFavicon; private ImageView mLockIcon; + public NavigationBarBase(Context context) { super(context); } @@ -73,6 +75,7 @@ public class NavigationBarBase extends LinearLayout implements protected void onFinishInflate() { super.onFinishInflate(); mLockIcon = (ImageView) findViewById(R.id.lock); + mLocationButton = (LocationButton) findViewById(R.id.location_button); mFavicon = (ImageView) findViewById(R.id.favicon); mUrlInput = (UrlInputView) findViewById(R.id.url); mUrlInput.setUrlInputListener(this); @@ -394,6 +397,7 @@ public class NavigationBarBase extends LinearLayout implements } public void onTabDataChanged(Tab tab) { + mLocationButton.onTabDataChanged(tab); } public void onVoiceResult(String s) { diff --git a/src/com/android/browser/NavigationBarPhone.java b/src/com/android/browser/NavigationBarPhone.java index a5257d1d..b6bd52c4 100644 --- a/src/com/android/browser/NavigationBarPhone.java +++ b/src/com/android/browser/NavigationBarPhone.java @@ -78,7 +78,7 @@ public class NavigationBarPhone extends NavigationBarBase implements mMagnify = (ImageView) findViewById(R.id.magnify); mTabSwitcher = findViewById(R.id.tab_switcher); mTabSwitcher.setOnClickListener(this); - mMore = findViewById(R.id.more); + mMore = findViewById(R.id.more_browser_settings); mMore.setOnClickListener(this); mComboIcon = findViewById(R.id.iconcombo); mComboIcon.setOnClickListener(this); diff --git a/src/com/android/browser/PhoneUi.java b/src/com/android/browser/PhoneUi.java index 17de4647..d83d81db 100644 --- a/src/com/android/browser/PhoneUi.java +++ b/src/com/android/browser/PhoneUi.java @@ -25,6 +25,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; +import android.os.Handler; import android.os.Message; import android.util.Log; import android.util.TypedValue; @@ -57,6 +58,8 @@ public class PhoneUi extends BaseUi { boolean mAnimating; boolean mShowNav = false; + static final int POST_DELAY = 300; + /** * @param browser * @param controller @@ -243,6 +246,14 @@ public class PhoneUi extends BaseUi { @Override public void onActionModeFinished(boolean inLoad) { super.onActionModeFinished(inLoad); + mTitleBar.animate().translationY(0); + stopEditingUrl(); + Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + public void run() { + mNavigationBar.onStateChanged(StateListener.STATE_NORMAL); + }}, POST_DELAY); + if (inLoad) { if (mUseQuickControls) { mTitleBar.setShowProgressOnly(true); @@ -302,9 +313,6 @@ public class PhoneUi extends BaseUi { int toRight = toLeft + width; int toBottom = toTop + height; float scaleFactor = width / (float) mContentView.getWidth(); - // SWE: Detaching the active tab results flashing screen with SWE. - // Not detaching the tab doesn't seem to have any issues. - //detachTab(mActiveTab); mContentView.setVisibility(View.GONE); AnimatorSet set1 = new AnimatorSet(); AnimatorSet inanim = new AnimatorSet(); @@ -378,7 +386,7 @@ public class PhoneUi extends BaseUi { if (mAnimScreen == null) { mAnimScreen = new AnimScreen(mActivity); } - mAnimScreen.set(tab.getScreenshot()); + mAnimScreen.set(tab.getFullScreenshot()); if (mAnimScreen.mMain.getParent() == null) { mCustomViewContainer.addView(mAnimScreen.mMain, COVER_SCREEN_PARAMS); } @@ -395,12 +403,12 @@ public class PhoneUi extends BaseUi { toTop = (tab.getWebView() != null) ? tab.getWebView().getVisibleTitleHeight() : 0; } int toRight = mContentView.getWidth(); - int width = target.getDrawable().getIntrinsicWidth(); - int height = target.getDrawable().getIntrinsicHeight(); + int width = mContentView.getWidth(); + int height = mContentView.getHeight(); int fromLeft = tabview.getLeft() + target.getLeft() - mNavScreen.mScroller.getScrollX(); int fromTop = tabview.getTop() + target.getTop() - mNavScreen.mScroller.getScrollY(); - int fromRight = fromLeft + width; - int fromBottom = fromTop + height; + int fromRight = fromLeft + target.getDrawable().getIntrinsicWidth(); + int fromBottom = fromTop + target.getDrawable().getIntrinsicHeight(); float scaleFactor = mContentView.getWidth() / (float) width; int toBottom = toTop + (int) (height * scaleFactor); mAnimScreen.mContent.setLeft(fromLeft); @@ -437,37 +445,50 @@ public class PhoneUi extends BaseUi { combo.start(); } + + private int mNumTries = 0; private void checkTabReady() { boolean isready = true; Tab tab = mUiController.getTabControl().getCurrentTab(); + BrowserWebView webview = null; if (tab == null) isready = false; else { - BrowserWebView webview = (BrowserWebView)tab.getWebView(); - if (webview == null) + webview = (BrowserWebView)tab.getWebView(); + if (webview == null) { isready = false; - else + } + else if (webview.hasCrashed()) { + webview.reload(); + isready = true; + } else { isready = webview.isReady(); + } } - android.os.Handler handler = mCustomViewContainer.getHandler(); - if (!isready) { - handler.postDelayed(new Runnable() { + // Post only when not ready and not crashed + if (!isready && mNumTries++ < 150) { + mCustomViewContainer.postDelayed(new Runnable() { public void run() { checkTabReady(); } }, 17); //WebView is not ready. check again in for next frame. return; } - handler.postDelayed(new Runnable() { + mNumTries = 0; + final boolean hasCrashed = (webview == null) ? false : webview.hasCrashed(); + mCustomViewContainer.postDelayed(new Runnable() { public void run() { - fadeOutCustomViewContainer(); + fadeOutCustomViewContainer(hasCrashed); } - }, 33); //WebView is ready, but give it extra 2 frame's time to display and finish the swaps + }, 33); //WebView is ready, but give it extra 2 frame's time to display and finish the swaps } - private void fadeOutCustomViewContainer() { + private void fadeOutCustomViewContainer(boolean hasCrashed) { ObjectAnimator otheralpha = ObjectAnimator.ofFloat(mCustomViewContainer, "alpha", 1f, 0f); - otheralpha.setDuration(100); + if (hasCrashed) + otheralpha.setDuration(300); + else + otheralpha.setDuration(100); otheralpha.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator anim) { diff --git a/src/com/android/browser/PreferenceKeys.java b/src/com/android/browser/PreferenceKeys.java index 8620053e..5860cd46 100644 --- a/src/com/android/browser/PreferenceKeys.java +++ b/src/com/android/browser/PreferenceKeys.java @@ -100,6 +100,7 @@ public interface PreferenceKeys { static final String PREF_REMEMBER_PASSWORDS = "remember_passwords"; static final String PREF_SAVE_FORMDATA = "save_formdata"; static final String PREF_SHOW_SECURITY_WARNINGS = "show_security_warnings"; + static final String PREF_DO_NOT_TRACK = "do_not_track"; // ---------------------- // Keys for bandwidth_preferences.xml diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java index 1b0b68c4..c9e785fc 100644 --- a/src/com/android/browser/Tab.java +++ b/src/com/android/browser/Tab.java @@ -175,6 +175,8 @@ class Tab implements PictureListener { private String mAppId; // flag to indicate if tab should be closed on back private boolean mCloseOnBack; + // flag to indicate if the tab was opened from an intent + private boolean mDerivedFromIntent = false; // Keep the original url around to avoid killing the old WebView if the url // has not changed. // Error console for the tab @@ -185,6 +187,8 @@ class Tab implements PictureListener { // Listener used to know when we move forward or back in the history list. private final WebBackForwardListClient mWebBackForwardListClient; private DataController mDataController; + //Indicates if a JS interface was created for a specific url + private boolean mJsInterfaceEnabled = false; // AsyncTask for downloading touch icons DownloadTouchIcon mTouchIconLoader; @@ -193,6 +197,7 @@ class Tab implements PictureListener { private int mCaptureWidth; private int mCaptureHeight; private Bitmap mCapture; + private Bitmap mScreenShot; private Handler mHandler; private boolean mUpdateThumbnail; private Timestamp timestamp; @@ -643,8 +648,10 @@ class Tab implements PictureListener { @Override public void onRendererCrash(WebView view, boolean crashedWhileOomProtected) { Log.e(LOGTAG, "Tab Crashed"); - hasCrashed = true; - showCrashView(); + if (mWebViewController.getTabControl().getCurrentTab() == Tab.this) { + hasCrashed = true; + showCrashView(); + } } /** @@ -1538,6 +1545,7 @@ class Tab implements PictureListener { } void pause() { + capture(); if (mMainView != null) { mMainView.onPause(); if (mSubView != null) { @@ -1568,7 +1576,6 @@ class Tab implements PictureListener { if (!mInForeground) { return; } - capture(); mInForeground = false; pause(); mMainView.setOnCreateContextMenuListener(null); @@ -1676,6 +1683,14 @@ class Tab implements PictureListener { mCloseOnBack = close; } + boolean getDerivedFromIntent() { + return mDerivedFromIntent; + } + + void setDerivedFromIntent(boolean derived) { + mDerivedFromIntent = derived; + } + String getUrl() { return UrlUtils.filteredUrl(mCurrentState.mUrl); } @@ -1863,6 +1878,12 @@ class Tab implements PictureListener { } } + public Bitmap getFullScreenshot() { + synchronized (Tab.this) { + return mScreenShot; + } + } + public boolean isSnapshot() { return false; } @@ -1950,11 +1971,27 @@ class Tab implements PictureListener { mPageLoadProgress = INITIAL_PROGRESS; mInPageLoad = true; mCurrentState = new PageState(mContext, false, url, null); + handleJsInterface(mMainView, url); mWebViewController.onPageStarted(this, mMainView, null); mMainView.loadUrl(url, headers); } } + public void handleJsInterface(WebView webview, String url){ + if (url != null && + url.equals(mContext.getResources().getString(R.string.homepage_base)) && + url.startsWith("file:///")) { + mJsInterfaceEnabled = true; + webview.getSettings().setJavaScriptEnabled(true); + webview.addJavascriptInterface(mContext, "default_homepage"); + } else { + if (mJsInterfaceEnabled) { + webview.removeJavascriptInterface("default_homepage"); + mJsInterfaceEnabled = false; + } + } + } + public void disableUrlOverridingForLoad() { mDisableOverrideUrlLoading = true; } @@ -1966,19 +2003,32 @@ class Tab implements PictureListener { } Canvas c = new Canvas(mCapture); int state = c.save(); + float scale = 0; Bitmap screenShot = mMainView.getViewportBitmap(); + mScreenShot = screenShot; if (screenShot != null) { - mCapture.eraseColor(Color.WHITE); - float scale = (float) mCaptureWidth / screenShot.getWidth(); - c.scale(scale, scale); - c.drawBitmap(screenShot, 0, 0, null); - } else { - final int left = mMainView.getViewScrollX(); - final int top = mMainView.getViewScrollY() + mMainView.getVisibleTitleHeight(); - c.translate(-left, -top); - float scale = mCaptureWidth / (float) mMainView.getWidth(); - c.scale(scale, scale, left, top); - if (mMainView instanceof BrowserWebView) { + //scale based on device orientation + if (screenShot.getHeight() > screenShot.getWidth()){ + scale = (float) mCaptureWidth / screenShot.getWidth(); + } else { + scale = (float) mCaptureHeight / screenShot.getHeight(); + } + mCapture.eraseColor(Color.WHITE); + c.scale(scale, scale); + c.drawBitmap(screenShot, 0, 0, null); + } else { + final int left = mMainView.getViewScrollX(); + final int top = mMainView.getViewScrollY() + mMainView.getVisibleTitleHeight(); + + if (mMainView.getHeight() > mMainView.getWidth()){ + scale = mCaptureWidth / (float) mMainView.getWidth(); + } else { + scale = mCaptureHeight / (float) mMainView.getHeight(); + } + + c.translate(-left, -top); + c.scale(scale, scale, left, top); + if (mMainView instanceof BrowserWebView) { ((BrowserWebView)mMainView).drawContent(c); } else { mMainView.draw(c); diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java index a9da8cf3..33f60ff3 100644 --- a/src/com/android/browser/TabControl.java +++ b/src/com/android/browser/TabControl.java @@ -19,6 +19,7 @@ package com.android.browser; import android.os.Bundle; import android.util.Log; +import org.codeaurora.swe.GeolocationPermissions; import org.codeaurora.swe.WebView; import java.util.ArrayList; @@ -50,6 +51,8 @@ class TabControl { private int mCurrentTab = -1; // the main browser controller private final Controller mController; + // number of incognito tabs + private int mNumIncognito = 0; private OnThumbnailUpdatedListener mOnThumbnailUpdatedListener; @@ -204,6 +207,9 @@ class TabControl { // Create a new tab and add it to the tab list Tab t = new Tab(mController, w, state); mTabs.add(t); + if (privateBrowsing) { + mNumIncognito += 1; + } // Initially put the tab in the background. t.putInBackground(); return t; @@ -248,6 +254,14 @@ class TabControl { // Remove t from our list of tabs. mTabs.remove(t); + //Clear incognito geolocation state if this is the last incognito tab. + if (t.isPrivateBrowsingEnabled()) { + mNumIncognito -= 1; + if (mNumIncognito == 0) { + GeolocationPermissions.onIncognitoTabsRemoved(); + } + } + // Put the tab in the background only if it is the current one. if (current == t) { t.putInBackground(); @@ -410,6 +424,9 @@ class TabControl { // sNextId to be set correctly. continue; } + //handle restored pages that may require a JS interface + t.handleJsInterface(t.getWebView(), t.getUrl()); + tabMap.put(id, t); // Me must set the current tab before restoring the state // so that all the client classes are set. diff --git a/src/com/android/browser/UrlInputView.java b/src/com/android/browser/UrlInputView.java index c8f2d401..f0037a12 100644 --- a/src/com/android/browser/UrlInputView.java +++ b/src/com/android/browser/UrlInputView.java @@ -71,6 +71,7 @@ public class UrlInputView extends AutoCompleteTextView static final int POST_DELAY = 100; static final int URL_MAX_LENGTH = 2048; + static final int POST_DELAY_FOCUS = 300; static interface StateListener { static final int STATE_NORMAL = 0; @@ -181,11 +182,11 @@ public class UrlInputView extends AutoCompleteTextView state = StateListener.STATE_NORMAL; } final int s = state; - post(new Runnable() { + postDelayed(new Runnable() { public void run() { changeState(s); } - }); + }, POST_DELAY_FOCUS); } @Override diff --git a/src/com/android/browser/mynavigation/AddMyNavigationPage.java b/src/com/android/browser/mynavigation/AddMyNavigationPage.java index e750aa2a..cc42d96a 100755 --- a/src/com/android/browser/mynavigation/AddMyNavigationPage.java +++ b/src/com/android/browser/mynavigation/AddMyNavigationPage.java @@ -77,11 +77,7 @@ public class AddMyNavigationPage extends Activity { private View.OnClickListener mOKListener = new View.OnClickListener() { public void onClick(View v) { - if (save()) { - AddMyNavigationPage.this.setResult(Activity.RESULT_OK, - (new Intent()).putExtra("need_refresh", true)); - finish(); - } + save(); } }; @@ -181,6 +177,8 @@ public class AddMyNavigationPage extends Activity { Uri uri = ContentUris.withAppendedId(MyNavigationUtil.MY_NAVIGATION_URI, cursor.getLong(0)); cr.update(uri, values, null, null); + AddMyNavigationPage.this.setResult(Activity.RESULT_OK, + (new Intent()).putExtra("need_refresh", true)); } else { Log.e(LOGTAG, "this item does not exist!"); } @@ -189,6 +187,7 @@ public class AddMyNavigationPage extends Activity { } finally { if (null != cursor) { cursor.close(); + AddMyNavigationPage.this.finish(); } } } diff --git a/src/com/android/browser/preferences/WebsiteSettingsFragment.java b/src/com/android/browser/preferences/WebsiteSettingsFragment.java index a621dece..38c19260 100644 --- a/src/com/android/browser/preferences/WebsiteSettingsFragment.java +++ b/src/com/android/browser/preferences/WebsiteSettingsFragment.java @@ -23,6 +23,8 @@ import android.content.DialogInterface; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -52,6 +54,7 @@ import java.util.Set; import org.codeaurora.swe.GeolocationPermissions; import org.codeaurora.swe.WebStorage; +import org.json.JSONArray; /** * Manage the settings for an origin. @@ -61,11 +64,15 @@ import org.codeaurora.swe.WebStorage; public class WebsiteSettingsFragment extends ListFragment implements OnClickListener { private static final String EXTRA_SITE = "site"; + private static final long MILLIS_PER_DAY = 86400000; private String LOGTAG = "WebsiteSettingsActivity"; private static String sMBStored = null; private SiteAdapter mAdapter = null; private Site mSite = null; + protected long geolocationPolicyExpiration; + protected boolean geolocationPolicyOriginAllowed; + static class Site implements Parcelable { private String mOrigin; private String mTitle; @@ -601,22 +608,101 @@ public class WebsiteSettingsFragment extends ListFragment implements OnClickList .show(); break; case Site.FEATURE_GEOLOCATION: - new AlertDialog.Builder(getContext()) - .setMessage(R.string.geolocation_settings_page_dialog_message) + final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + final String origin = mCurrentSite.getOrigin(); + final GeolocationPermissions geolocationPermissions + = GeolocationPermissions.getInstance(); + + DialogInterface.OnClickListener alertDialogListener = + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dlg, int which) { + GeolocationPermissions geolocationPermissions = + GeolocationPermissions.getInstance(); + String origin = mCurrentSite.getOrigin(); + int selectedPosition = ((AlertDialog)dlg) + .getListView().getCheckedItemPosition(); + switch (selectedPosition) { + case 0: // Deny forever + geolocationPermissions.deny(origin); + break; + case 1: // Allow for 24 hours + // encode the expiration time and origin as a JSON string + JSONArray jsonArray = new JSONArray(); + jsonArray.put(System.currentTimeMillis() + MILLIS_PER_DAY); + jsonArray.put(origin); + geolocationPermissions.allow(jsonArray.toString()); + break; + case 2: // Allow forever + geolocationPermissions.allow(origin); + break; + case 3: // Always ask + geolocationPermissions.clear(origin); + mCurrentSite.removeFeature(Site.FEATURE_GEOLOCATION); + if (mCurrentSite.getFeatureCount() == 0) { + finish(); + } + break; + default: + break; + } + askForOrigins(); + notifyDataSetChanged(); + }}; + + builder.setTitle(String.format(getResources() + .getString(R.string.geolocation_settings_page_dialog_title), + "http".equals(Uri.parse(mCurrentSite.getOrigin()).getScheme()) ? + origin.substring(7) : origin )) .setPositiveButton(R.string.geolocation_settings_page_dialog_ok_button, - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dlg, int which) { - GeolocationPermissions.getInstance().clear(mCurrentSite.getOrigin()); - mCurrentSite.removeFeature(Site.FEATURE_GEOLOCATION); - if (mCurrentSite.getFeatureCount() == 0) { - finish(); + alertDialogListener) + .setNegativeButton(R.string.geolocation_settings_page_dialog_cancel_button, null); + + final ValueCallback<Long> getExpirationCallback = + new ValueCallback<Long>() { + public void onReceiveValue(Long expirationTime) { + if (expirationTime != null) { + geolocationPolicyExpiration = expirationTime.longValue(); + // Set radio button and location icon + if (!geolocationPolicyOriginAllowed) { + // 0: Deny forever + builder.setSingleChoiceItems(R.array.geolocation_settings_choices, 0, null); + } else { + if (geolocationPolicyExpiration + != GeolocationPermissions.DO_NOT_EXPIRE) { + // 1: Allow for 24 hours + builder.setSingleChoiceItems(R.array.geolocation_settings_choices, 1, null); + } else { + // 2: Allow forever + builder.setSingleChoiceItems(R.array.geolocation_settings_choices, 2, null); + } } - askForOrigins(); - notifyDataSetChanged(); - }}) - .setNegativeButton(R.string.geolocation_settings_page_dialog_cancel_button, null) - .setIconAttribute(android.R.attr.alertDialogIcon) - .show(); + } + builder.show(); + } + }; + + final ValueCallback<Boolean> getAllowedCallback = + new ValueCallback<Boolean>() { + public void onReceiveValue(Boolean allowed) { + if (allowed != null) { + geolocationPolicyOriginAllowed = allowed.booleanValue(); + //Get the policy expiration time + geolocationPermissions.getExpirationTime(origin, + getExpirationCallback); + } + } + }; + + geolocationPermissions.hasOrigin(origin, + new ValueCallback<Boolean>() { + public void onReceiveValue(Boolean hasOrigin) { + if (hasOrigin != null && hasOrigin.booleanValue()) { + //Get whether origin is allowed or denied + geolocationPermissions.getAllowed(origin, + getAllowedCallback); + } + } + }); break; } } else { @@ -691,6 +777,9 @@ public class WebsiteSettingsFragment extends ListFragment implements OnClickList public void onClick(DialogInterface dlg, int which) { WebStorage.getInstance().deleteAllData(); GeolocationPermissions.getInstance().clearAll(); + if (GeolocationPermissions.isIncognitoCreated()) { + GeolocationPermissions.getIncognitoInstance().clearAll(); + } WebStorageSizeManager.resetLastOutOfSpaceNotificationTime(); mAdapter.askForOrigins(); finish(); diff --git a/src_system/AndroidManifest.xml b/src_system/AndroidManifest.xml index 000cf9d6..63a0cd1f 100644 --- a/src_system/AndroidManifest.xml +++ b/src_system/AndroidManifest.xml @@ -309,7 +309,8 @@ <provider android:name=".provider.MyNavigationProvider" android:authorities="com.android.browser.mynavigation" - android:exported="false" /> + android:exported="false" + android:grantUriPermissions="true"/> <service android:name="org.chromium.content.app.SandboxedProcessService0" android:process=":sandboxed_process0" |