Merge "Add char limit for localization."
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index b62862b..3535725 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -116,6 +116,7 @@
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLEncoder;
+import java.util.Calendar;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -276,7 +277,19 @@
                 }
             };
 
-        if (!mTabControl.restoreState(icicle)) {
+        // Unless the last browser usage was within 24 hours, destroy any
+        // remaining incognito tabs.
+
+        Calendar lastActiveDate = icicle != null ? (Calendar) icicle.getSerializable("lastActiveDate") : null;
+        Calendar today = Calendar.getInstance();
+        Calendar yesterday = Calendar.getInstance();
+        yesterday.add(Calendar.DATE, -1);
+
+        boolean dontRestoreIncognitoTabs = lastActiveDate == null
+            || lastActiveDate.before(yesterday)
+            || lastActiveDate.after(today);
+
+        if (!mTabControl.restoreState(icicle, dontRestoreIncognitoTabs)) {
             // clear up the thumbnail directory if we can't restore the state as
             // none of the files in the directory are referenced any more.
             new ClearThumbnails().execute(
@@ -284,6 +297,8 @@
             // there is no quit on Android. But if we can't restore the state,
             // we can treat it as a new Browser, remove the old session cookies.
             CookieManager.getInstance().removeSessionCookie();
+            // remove any incognito files
+            WebView.cleanupPrivateBrowsingFiles(this);
             final Intent intent = getIntent();
             final Bundle extra = intent.getExtras();
             // Create an initial tab.
@@ -316,6 +331,10 @@
                 loadUrlDataIn(t, urlData);
             }
         } else {
+            if (dontRestoreIncognitoTabs) {
+                WebView.cleanupPrivateBrowsingFiles(this);
+            }
+
             // TabControl.restoreState() will create a new tab even if
             // restoring the state fails.
             attachTabToContentView(mTabControl.getCurrentTab());
@@ -930,6 +949,9 @@
 
         // Save all the tabs
         mTabControl.saveState(outState);
+
+        // Save time so that we know how old incognito tabs (if any) are.
+        outState.putSerializable("lastActiveDate", Calendar.getInstance());
     }
 
     @Override
@@ -1392,17 +1414,26 @@
                 if (LOGD_ENABLED) {
                     Log.d(LOGTAG, "Save as Web Archive");
                 }
-                String directory = getExternalFilesDir(null).getAbsolutePath() + File.separator;
-                getTopWindow().saveWebArchive(directory, true, new ValueCallback<String>() {
-                    @Override
-                    public void onReceiveValue(String value) {
-                        if (value != null) {
-                            Toast.makeText(BrowserActivity.this, R.string.webarchive_saved, Toast.LENGTH_SHORT).show();
-                        } else {
-                            Toast.makeText(BrowserActivity.this, R.string.webarchive_failed, Toast.LENGTH_SHORT).show();
+                String state = Environment.getExternalStorageState();
+                if (Environment.MEDIA_MOUNTED.equals(state)) {
+                    String directory = Environment.getExternalStoragePublicDirectory(
+                            Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + File.separator;
+                    getTopWindow().saveWebArchive(directory, true, new ValueCallback<String>() {
+                        @Override
+                        public void onReceiveValue(String value) {
+                            if (value != null) {
+                                Toast.makeText(BrowserActivity.this, R.string.webarchive_saved,
+                                        Toast.LENGTH_SHORT).show();
+                            } else {
+                                Toast.makeText(BrowserActivity.this, R.string.webarchive_failed,
+                                        Toast.LENGTH_SHORT).show();
+                            }
                         }
-                    }
-                });
+                    });
+                } else {
+                    Toast.makeText(BrowserActivity.this, R.string.webarchive_failed,
+                            Toast.LENGTH_SHORT).show();
+                }
                 break;
 
             case R.id.page_info_menu_id:
@@ -2086,6 +2117,10 @@
         mTabControl.setCurrentTab(mTabControl.getTab(currentIndex));
         resetTitleIconAndProgress();
         updateLockIconToLatest();
+
+        if (!mTabControl.hasAnyOpenIncognitoTabs()) {
+            WebView.cleanupPrivateBrowsingFiles(this);
+        }
     }
 
     /* package */ void goBackOnePageOrQuit() {
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index 87c38e2..33f3006 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -385,7 +385,7 @@
                 fixPicasaBookmark();
                 Editor ed = p.edit();
                 ed.putBoolean("fix_picasa", false);
-                ed.commit();
+                ed.apply();
             }
         }
         mSearchManager = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 2f739fa..5a7dd0d 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -401,7 +401,7 @@
         Editor ed = PreferenceManager.
                 getDefaultSharedPreferences(context).edit();
         ed.putString(PREF_HOMEPAGE, url);
-        ed.commit();
+        ed.apply();
         homeUrl = url;
     }
 
@@ -581,7 +581,7 @@
         reset();
         SharedPreferences p =
             PreferenceManager.getDefaultSharedPreferences(ctx);
-        p.edit().clear().commit();
+        p.edit().clear().apply();
         PreferenceManager.setDefaultValues(ctx, R.xml.browser_preferences,
                 true);
         // reset homeUrl
diff --git a/src/com/android/browser/SystemAllowGeolocationOrigins.java b/src/com/android/browser/SystemAllowGeolocationOrigins.java
index 3f5a84e..b53611f 100644
--- a/src/com/android/browser/SystemAllowGeolocationOrigins.java
+++ b/src/com/android/browser/SystemAllowGeolocationOrigins.java
@@ -103,7 +103,7 @@
         // Save the new value as the last read value
         preferences.edit()
                 .putString(LAST_READ_ALLOW_GEOLOCATION_ORIGINS, newSetting)
-                .commit();
+                .apply();
 
         Set<String> oldOrigins = parseAllowGeolocationOrigins(lastReadSetting);
         Set<String> newOrigins = parseAllowGeolocationOrigins(newSetting);
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index a9ab058..7c52bb6 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -156,6 +156,7 @@
     static final String PARENTTAB = "parentTab";
     static final String APPID = "appid";
     static final String ORIGINALURL = "originalUrl";
+    static final String INCOGNITO = "privateBrowsingEnabled";
 
     // -------------------------------------------------------------------------
 
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index 333ce91..7377a1e 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -29,6 +29,7 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Vector;
 
 class TabControl {
@@ -150,6 +151,19 @@
     }
 
     /**
+     * Returns true if there are any incognito tabs open.
+     * @return True when any incognito tabs are open, false otherwise.
+     */
+    boolean hasAnyOpenIncognitoTabs() {
+        for (Tab tab : mTabs) {
+            if (tab.getWebView() != null && tab.getWebView().isPrivateBrowsingEnabled()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Create a new tab.
      * @return The newly createTab or null if we have reached the maximum
      *         number of open tabs.
@@ -284,29 +298,56 @@
      * @return True if there were previous tabs that were restored. False if
      *         there was no saved state or restoring the state failed.
      */
-    boolean restoreState(Bundle inState) {
+    boolean restoreState(Bundle inState, boolean dontRestoreIncognitoTabs) {
         final int numTabs = (inState == null)
                 ? -1 : inState.getInt(Tab.NUMTABS, -1);
         if (numTabs == -1) {
             return false;
         } else {
-            final int currentTab = inState.getInt(Tab.CURRTAB, -1);
+            final int oldCurrentTab = inState.getInt(Tab.CURRTAB, -1);
+
+            // Determine whether the saved current tab can be restored, and
+            // if not, which tab will take its place.
+            int currentTab = -1;
+            if (!dontRestoreIncognitoTabs
+                    || !inState.getBundle(Tab.WEBVIEW + oldCurrentTab).getBoolean(Tab.INCOGNITO)) {
+                currentTab = oldCurrentTab;
+            } else {
+                for (int i = 0; i < numTabs; i++) {
+                    if (!inState.getBundle(Tab.WEBVIEW + i).getBoolean(Tab.INCOGNITO)) {
+                        currentTab = i;
+                        break;
+                    }
+                }
+            }
+            if (currentTab < 0) {
+                return false;
+            }
+
+            // Map saved tab indices to new indices, in case any incognito tabs
+            // need to not be restored.
+            HashMap<Integer, Integer> originalTabIndices = new HashMap<Integer, Integer>();
+            originalTabIndices.put(-1, -1);
             for (int i = 0; i < numTabs; i++) {
-                if (i == currentTab) {
+                Bundle state = inState.getBundle(Tab.WEBVIEW + i);
+
+                if (dontRestoreIncognitoTabs && state != null && state.getBoolean(Tab.INCOGNITO)) {
+                    originalTabIndices.put(i, -1);
+                } else if (i == currentTab) {
                     Tab t = createNewTab();
                     // Me must set the current tab before restoring the state
                     // so that all the client classes are set.
                     setCurrentTab(t);
-                    if (!t.restoreState(inState.getBundle(Tab.WEBVIEW + i))) {
+                    if (!t.restoreState(state)) {
                         Log.w(LOGTAG, "Fail in restoreState, load home page.");
                         t.getWebView().loadUrl(BrowserSettings.getInstance()
                                 .getHomePage());
                     }
+                    originalTabIndices.put(i, getTabCount() - 1);
                 } else {
                     // Create a new tab and don't restore the state yet, add it
                     // to the tab list
                     Tab t = new Tab(mActivity, null, false, null, null);
-                    Bundle state = inState.getBundle(Tab.WEBVIEW + i);
                     if (state != null) {
                         t.setSavedState(state);
                         t.populatePickerDataFromSavedState();
@@ -318,15 +359,17 @@
                     mTabs.add(t);
                     // added the tab to the front as they are not current
                     mTabQueue.add(0, t);
+                    originalTabIndices.put(i, getTabCount() - 1);
                 }
             }
+
             // Rebuild the tree of tabs. Do this after all tabs have been
             // created/restored so that the parent tab exists.
             for (int i = 0; i < numTabs; i++) {
                 final Bundle b = inState.getBundle(Tab.WEBVIEW + i);
                 final Tab t = getTab(i);
                 if (b != null && t != null) {
-                    final int parentIndex = b.getInt(Tab.PARENTTAB, -1);
+                    final Integer parentIndex = originalTabIndices.get(b.getInt(Tab.PARENTTAB, -1));
                     if (parentIndex != -1) {
                         final Tab parent = getTab(parentIndex);
                         if (parent != null) {
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index 5825525..cba585e 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -31,13 +31,13 @@
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.provider.BrowserContract;
+import android.provider.SyncStateContract;
 import android.provider.BrowserContract.Bookmarks;
 import android.provider.BrowserContract.ChromeSyncColumns;
 import android.provider.BrowserContract.History;
 import android.provider.BrowserContract.Searches;
 import android.provider.BrowserContract.SyncState;
 import android.provider.ContactsContract.RawContacts;
-import android.provider.SyncStateContract;
 import android.text.TextUtils;
 
 import java.util.HashMap;
@@ -73,6 +73,7 @@
     static final long FIXED_ID_OTHER_BOOKMARKS = 4;
 
     static final String DEFAULT_BOOKMARKS_SORT_ORDER = "position ASC, _id ASC";
+
     
     static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
 
@@ -112,6 +113,8 @@
         map.put(Bookmarks.ACCOUNT_TYPE, Bookmarks.ACCOUNT_TYPE);
         map.put(Bookmarks.SOURCE_ID, Bookmarks.SOURCE_ID);
         map.put(Bookmarks.VERSION, Bookmarks.VERSION);
+        map.put(Bookmarks.DATE_CREATED, Bookmarks.DATE_CREATED);
+        map.put(Bookmarks.DATE_MODIFIED, Bookmarks.DATE_MODIFIED);
         map.put(Bookmarks.DIRTY, Bookmarks.DIRTY);
         map.put(Bookmarks.SYNC1, Bookmarks.SYNC1);
         map.put(Bookmarks.SYNC2, Bookmarks.SYNC2);
@@ -148,13 +151,13 @@
     static final String qualifyColumn(String table, String column) {
         return table + "." + column + " AS " + column;
     }
-    
+
     DatabaseHelper mOpenHelper;
     SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper();
 
     final class DatabaseHelper extends SQLiteOpenHelper {
         static final String DATABASE_NAME = "browser2.db";
-        static final int DATABASE_VERSION = 15;
+        static final int DATABASE_VERSION = 16;
         public DatabaseHelper(Context context) {
             super(context, DATABASE_NAME, null, DATABASE_VERSION);
         }
@@ -177,6 +180,8 @@
                     Bookmarks.ACCOUNT_TYPE + " TEXT," +
                     Bookmarks.SOURCE_ID + " TEXT," +
                     Bookmarks.VERSION + " INTEGER NOT NULL DEFAULT 1," +
+                    Bookmarks.DATE_CREATED + " INTEGER," +
+                    Bookmarks.DATE_MODIFIED + " INTEGER," +
                     Bookmarks.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
                     Bookmarks.SYNC1 + " TEXT," +
                     Bookmarks.SYNC2 + " TEXT," +
@@ -224,6 +229,7 @@
         public void onOpen(SQLiteDatabase db) {
             mSyncHelper.onDatabaseOpened(db);
         }
+
         
         private void createDefaultBookmarks(SQLiteDatabase db) {
             ContentValues values = new ContentValues();
@@ -451,7 +457,7 @@
                 if (TextUtils.isEmpty(sortOrder)) {
                     sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
                 }
-                
+
                 qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
                 qb.setTables(TABLE_BOOKMARKS);
                 break;
@@ -535,6 +541,7 @@
                     // If the caller isn't a sync adapter just go through and update all the
                     // bookmarks to have the deleted flag set.
                     ContentValues values = new ContentValues();
+                    values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
                     values.put(Bookmarks.IS_DELETED, 1);
                     return updateInTransaction(uri, values, selection, selectionArgs,
                             callerIsSyncAdapter);
@@ -592,6 +599,9 @@
             case BOOKMARKS: {
                 // Mark rows dirty if they're not coming from a sync adapater
                 if (!callerIsSyncAdapter) {
+                    long now = System.currentTimeMillis();
+                    values.put(Bookmarks.DATE_CREATED, now);
+                    values.put(Bookmarks.DATE_MODIFIED, now);
                     values.put(Bookmarks.DIRTY, 1);
                 }
 
@@ -615,12 +625,12 @@
             }
 
             case SEARCHES: {
-                id = db.insertOrThrow(TABLE_SEARCHES, Searches.SEARCH, values);
+                id = insertSearchesInTransaction(db, values);
                 break;
             }
 
             case SYNCSTATE: {
-                id = mSyncHelper.insert(mDb, values);
+                id = mSyncHelper.insert(db, values);
                 break;
             }
 
@@ -636,6 +646,31 @@
         }
     }
 
+    /**
+     * Searches are unique, so perform an UPSERT manually since SQLite doesn't support them.
+     */
+    private long insertSearchesInTransaction(SQLiteDatabase db, ContentValues values) {
+        String search = values.getAsString(Searches.SEARCH);
+        if (TextUtils.isEmpty(search)) {
+            throw new IllegalArgumentException("Must include the SEARCH field");
+        }
+        Cursor cursor = null;
+        try {
+            cursor = db.query(TABLE_SEARCHES, new String[] { Searches._ID },
+                    Searches.SEARCH + "=?", new String[] { search }, null, null, null);
+            if (cursor.moveToNext()) {
+                long id = cursor.getLong(0);
+                db.update(TABLE_SEARCHES, values, Searches._ID + "=?",
+                        new String[] { Long.toString(id) });
+                return id;
+            } else {
+                return db.insertOrThrow(TABLE_SEARCHES, Searches.SEARCH, values);
+            }
+        } finally {
+            if (cursor != null) cursor.close();
+        }
+    }
+
     @Override
     public int updateInTransaction(Uri uri, ContentValues values, String selection,
             String[] selectionArgs, boolean callerIsSyncAdapter) {
@@ -664,16 +699,6 @@
                 return db.update(TABLE_HISTORY, values, selection, selectionArgs);
             }
 
-            case SEARCHES_ID: {
-                selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
-                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
-                        new String[] { Long.toString(ContentUris.parseId(uri)) });
-                // fall through
-            }
-            case SEARCHES: {
-                return db.update(TABLE_SEARCHES, values, selection, selectionArgs);
-            }
-
             case SYNCSTATE: {
                 return mSyncHelper.update(mDb, values,
                         appendAccountToSelection(uri, selection), selectionArgs);
@@ -706,6 +731,7 @@
             // Mark the bookmark dirty if the caller isn't a sync adapter
             if (!callerIsSyncAdapter) {
                 values = new ContentValues(values);
+                values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
                 values.put(Bookmarks.DIRTY, 1);
             }
             while (cursor.moveToNext()) {