Merge "Support fallback key events"
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index e7f804f..5fa73de 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -182,7 +182,7 @@
     <string name="autofill_profile_successful_delete" msgid="1790262652460723127">"ራስ ሰር-ሙላ ጽሑፍ ተሰርዟል::"</string>
     <string name="autofill_profile_editor_delete_profile" msgid="3098369314558164153">"ሰርዝ"</string>
     <string name="autofill_setup_dialog_message" msgid="7123905786076456503">"አሳሹ እንደዚህ ዓይነቶቹን የድር ቅርጾች በራስ ሰር ያሟላል፡፡ የራስህን ራስ-ሙላ ጽሑፍ ማዘጋጀት ትፈልጋለህ?"</string>
-    <string name="autofill_setup_dialog_negative_toast" msgid="3288881675232206984">"የራስህን ራስ ሰር ሙላ ጽሑፍ ከአሳሽ ላይ&gt;  ቅንጅቶች&gt;  አጠቃላይ ማያ ላይ ሁልጊዜ ማዘጋጀት ትችላለህ፡፡"</string>
+    <string name="autofill_setup_dialog_negative_toast" msgid="3288881675232206984">"የራስህን ራስ ሰር ሙላ ጽሑፍ ከአሳሽ ላይ&gt;  ቅንብሮች &gt;  አጠቃላይ ማያ ላይ ሁልጊዜ ማዘጋጀት ትችላለህ፡፡"</string>
     <string name="disable_autofill" msgid="8305901059849400354">"ራስ-ሙላ አቦዝን"</string>
     <string name="pref_privacy_security_title" msgid="3480313968942160914">"ግላዊነት&amp; ደህንነት"</string>
     <string name="pref_privacy_clear_cache" msgid="3380316479925886998">"መሸጎጫ አጥራ"</string>
@@ -334,7 +334,7 @@
     <string name="geolocation_permissions_prompt_share" msgid="9084486342048347976">"ሥፍራ አጋራ"</string>
     <string name="geolocation_permissions_prompt_dont_share" msgid="6303025160237860300">"አትቀበል"</string>
     <string name="geolocation_permissions_prompt_remember" msgid="3118526300707348308">"ምርጫ አስታውስ"</string>
-    <string name="geolocation_permissions_prompt_toast_allowed" msgid="7201417941112726112">"ይህ ድረ ገፅ የአንተን ሥፍራ መድረስ አይችልም። በቅንጅቶች ውስጥ ይህን-&gt; ከፍተኛ-&gt; የድረ ገፅ ቅንጅቶች ለውጥ::"</string>
+    <string name="geolocation_permissions_prompt_toast_allowed" msgid="7201417941112726112">"ይህ ድረ ገፅ የአንተን ሥፍራ መድረስ አይችልም። በቅንብሮች  ውስጥ ይህን-&gt; ከፍተኛ-&gt; የድረ ገፅ ቅንብሮች  ለውጥ::"</string>
     <string name="geolocation_permissions_prompt_toast_disallowed" msgid="156443445797377409">"ይህ ድረ ገፅ የአንተን ሥፍራ መድረስ አይችልም። በቅንጅቶች ውስጥ ይህን-&gt; ከፍተኛ-&gt; የድረ ገፅ ቅንብሮች ለውጥ::"</string>
     <string name="geolocation_settings_page_title" msgid="1745477985097536528">"የስፍራ መድረሻ አጥራ"</string>
     <string name="geolocation_settings_page_summary_allowed" msgid="9180251524290811398">"ይህ ድረ ገፅ በአሁኑ ጊዜ የእርስዎን ስፍራ መድረስ ይችላል"</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 95d383c..90ec428 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -215,6 +215,8 @@
     <string name="share_page">Share page</string>
     <!-- Menu item for saving a page for offline reading. This is a view-only snapshot of the page. [CHAR LIMIT=50] -->
     <string name="menu_save_snapshot">Save for offline reading</string>
+    <!-- Dialog message that is shown while saving a page for offline reading. [CHAR LIMIT=50] -->
+    <string name="saving_snapshot">Saving\u2026</string>
     <!-- Toast informing the user that saving the page for offline reading has failed. [CHAR LIMIT=50] -->
     <string name="snapshot_failed">Couldn\'t save for offline reading.</string>
     <!-- The number of bookmarks in a folder [CHAR LIMT=50] -->
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 415e72d..cddeda6 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -336,6 +336,9 @@
         settings.setAppCachePath(getAppCachePath());
         settings.setDatabasePath(mContext.getDir("databases", 0).getPath());
         settings.setGeolocationDatabasePath(mContext.getDir("geolocation", 0).getPath());
+        // origin policy for file access
+        settings.setAllowUniversalAccessFromFileURLs(false);
+        settings.setAllowFileAccessFromFileURLs(false);
     }
 
     private void syncSharedSettings() {
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 3ebaa28..36f6a1a 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -17,7 +17,10 @@
 package com.android.browser;
 
 import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
 import android.app.DownloadManager;
+import android.app.ProgressDialog;
 import android.app.SearchManager;
 import android.content.ClipboardManager;
 import android.content.ContentProvider;
@@ -26,6 +29,8 @@
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -1634,37 +1639,7 @@
             case R.id.save_snapshot_menu_id:
                 final Tab source = getTabControl().getCurrentTab();
                 if (source == null) break;
-                final ContentResolver cr = mActivity.getContentResolver();
-                final ContentValues values = source.createSnapshotValues();
-                if (values != null) {
-                    new AsyncTask<Tab, Void, Long>() {
-
-                        @Override
-                        protected Long doInBackground(Tab... params) {
-                            Uri result = cr.insert(Snapshots.CONTENT_URI, values);
-                            if (result == null) {
-                                return null;
-                            }
-                            long id = ContentUris.parseId(result);
-                            return id;
-                        }
-
-                        @Override
-                        protected void onPostExecute(Long id) {
-                            if (id == null) {
-                                Toast.makeText(mActivity, R.string.snapshot_failed,
-                                        Toast.LENGTH_SHORT).show();
-                                return;
-                            }
-                            Bundle b = new Bundle();
-                            b.putLong(BrowserSnapshotPage.EXTRA_ANIMATE_ID, id);
-                            mUi.showComboView(ComboViews.Snapshots, b);
-                        };
-                    }.execute(source);
-                } else {
-                    Toast.makeText(mActivity, R.string.snapshot_failed,
-                            Toast.LENGTH_SHORT).show();
-                }
+                new SaveSnapshotTask(source).execute();
                 break;
 
             case R.id.page_info_menu_id:
@@ -1732,6 +1707,69 @@
         return true;
     }
 
+    private class SaveSnapshotTask extends AsyncTask<Void, Void, Long>
+            implements OnCancelListener {
+
+        private Tab mTab;
+        private Dialog mProgressDialog;
+        private ContentValues mValues;
+
+        private SaveSnapshotTask(Tab tab) {
+            mTab = tab;
+        }
+
+        @Override
+        protected void onPreExecute() {
+            CharSequence message = mActivity.getText(R.string.saving_snapshot);
+            mProgressDialog = ProgressDialog.show(mActivity, null, message,
+                    true, true, this);
+            mValues = mTab.createSnapshotValues();
+        }
+
+        @Override
+        protected Long doInBackground(Void... params) {
+            if (!mTab.saveViewState(mValues)) {
+                return null;
+            }
+            if (isCancelled()) {
+                String path = mValues.getAsString(Snapshots.VIEWSTATE_PATH);
+                File file = mActivity.getFileStreamPath(path);
+                if (!file.delete()) {
+                    file.deleteOnExit();
+                }
+                return null;
+            }
+            final ContentResolver cr = mActivity.getContentResolver();
+            Uri result = cr.insert(Snapshots.CONTENT_URI, mValues);
+            if (result == null) {
+                return null;
+            }
+            long id = ContentUris.parseId(result);
+            return id;
+        }
+
+        @Override
+        protected void onPostExecute(Long id) {
+            if (isCancelled()) {
+                return;
+            }
+            mProgressDialog.dismiss();
+            if (id == null) {
+                Toast.makeText(mActivity, R.string.snapshot_failed,
+                        Toast.LENGTH_SHORT).show();
+                return;
+            }
+            Bundle b = new Bundle();
+            b.putLong(BrowserSnapshotPage.EXTRA_ANIMATE_ID, id);
+            mUi.showComboView(ComboViews.Snapshots, b);
+        }
+
+        @Override
+        public void onCancel(DialogInterface dialog) {
+            cancel(true);
+        }
+    }
+
     @Override
     public void toggleUserAgent() {
         WebView web = getCurrentWebView();
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 62f9548..cbda456 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -77,6 +77,7 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -2062,29 +2063,29 @@
         return false;
     }
 
-    public ContentValues createSnapshotValues() {
-        if (mMainView == null) return null;
-        String path = UUID.randomUUID().toString();
-        try {
-            OutputStream outs = mContext.openFileOutput(path, Context.MODE_PRIVATE);
-            GZIPOutputStream stream = new GZIPOutputStream(outs);
-            if (!getWebViewClassic().saveViewState(stream)) {
-                return null;
+    private static class SaveCallback implements ValueCallback<Boolean> {
+        boolean mResult;
+
+        @Override
+        public void onReceiveValue(Boolean value) {
+            mResult = value;
+            synchronized (this) {
+                notifyAll();
             }
-            stream.flush();
-            stream.close();
-        } catch (Exception e) {
-            Log.w(LOGTAG, "Failed to save view state", e);
-            return null;
         }
-        File savedFile = mContext.getFileStreamPath(path);
-        long size = savedFile.length();
+
+    }
+
+    /**
+     * Must be called on the UI thread
+     */
+    public ContentValues createSnapshotValues() {
+        WebViewClassic web = getWebViewClassic();
+        if (web == null) return null;
         ContentValues values = new ContentValues();
         values.put(Snapshots.TITLE, mCurrentState.mTitle);
         values.put(Snapshots.URL, mCurrentState.mUrl);
-        values.put(Snapshots.VIEWSTATE_PATH, path);
-        values.put(Snapshots.VIEWSTATE_SIZE, size);
-        values.put(Snapshots.BACKGROUND, getWebViewClassic().getPageBackgroundColor());
+        values.put(Snapshots.BACKGROUND, web.getPageBackgroundColor());
         values.put(Snapshots.DATE_CREATED, System.currentTimeMillis());
         values.put(Snapshots.FAVICON, compressBitmap(getFavicon()));
         Bitmap screenshot = Controller.createScreenshot(mMainView,
@@ -2094,6 +2095,50 @@
         return values;
     }
 
+    /**
+     * Probably want to call this on a background thread
+     */
+    public boolean saveViewState(ContentValues values) {
+        WebViewClassic web = getWebViewClassic();
+        if (web == null) return false;
+        String path = UUID.randomUUID().toString();
+        SaveCallback callback = new SaveCallback();
+        OutputStream outs = null;
+        try {
+            outs = mContext.openFileOutput(path, Context.MODE_PRIVATE);
+            GZIPOutputStream stream = new GZIPOutputStream(outs);
+            synchronized (callback) {
+                web.saveViewState(stream, callback);
+                callback.wait();
+            }
+            stream.flush();
+            stream.close();
+        } catch (Exception e) {
+            Log.w(LOGTAG, "Failed to save view state", e);
+            if (outs != null) {
+                try {
+                    outs.close();
+                } catch (IOException ignore) {}
+            }
+            File file = mContext.getFileStreamPath(path);
+            if (file.exists() && !file.delete()) {
+                file.deleteOnExit();
+            }
+            return false;
+        }
+        File savedFile = mContext.getFileStreamPath(path);
+        if (!callback.mResult) {
+            if (!savedFile.delete()) {
+                savedFile.deleteOnExit();
+            }
+            return false;
+        }
+        long size = savedFile.length();
+        values.put(Snapshots.VIEWSTATE_PATH, path);
+        values.put(Snapshots.VIEWSTATE_SIZE, size);
+        return true;
+    }
+
     public byte[] compressBitmap(Bitmap bitmap) {
         if (bitmap == null) {
             return null;