Merge "browser: Move SyncStateContentProviderHelper to frameworks/ex"
diff --git a/res/drawable/preview.png b/res/drawable/preview.png
new file mode 100644
index 0000000..f05710f
--- /dev/null
+++ b/res/drawable/preview.png
Binary files differ
diff --git a/res/values-xlarge/styles.xml b/res/values-xlarge/styles.xml
index 0420495..51dea1c 100644
--- a/res/values-xlarge/styles.xml
+++ b/res/values-xlarge/styles.xml
@@ -28,7 +28,6 @@
         <item name="android:actionModeBackground">@drawable/cab_bg</item>
         <item name="android:actionModeCloseDrawable">@drawable/ic_menu_cab_close</item>
         <item name="android:actionButtonStyle">@style/ActionButton</item>
-        <item name="android:popupMenuStyle">@style/ActionDropdown</item>
         <item name="android:actionOverflowButtonStyle">@style/Overflow</item>
     </style>
     <style name="Dialog.Holo" parent="android:Theme.Holo">
@@ -63,9 +62,6 @@
         <item name="android:height">12dip</item>
         <item name="android:background">@drawable/browserbarbutton</item>
     </style>
-    <style name="ActionDropdown" parent="@android:style/Widget.PopupMenu">
-        <item name="android:background">@drawable/menu_dropdown</item>
-    </style>
     <style name="Overflow">
         <item name="android:src">@drawable/ic_menu_overflow</item>
         <item name="android:paddingLeft">16dip</item>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index 9aae216..7508e00 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -18,4 +18,6 @@
     <integer name="max_suggest_lines_portrait">12</integer>
     <!--  The maximum number of open tabs -->
     <integer name="max_tabs">16</integer>
+    <!--  The duration of the tab animations in millisecs  -->
+    <integer name="tab_animation_duration">500</integer>
 </resources>
\ No newline at end of file
diff --git a/res/xml/bookmarkstackwidget.xml b/res/xml/bookmarkstackwidget.xml
index 1879923..56e7351 100644
--- a/res/xml/bookmarkstackwidget.xml
+++ b/res/xml/bookmarkstackwidget.xml
@@ -20,5 +20,6 @@
     android:minWidth="146dip"
     android:minHeight="146dip"
     android:updatePeriodMillis="3600000"
+    android:previewImage="@drawable/preview"
     android:initialLayout="@layout/bookmarkstackwidget">
 </appwidget-provider>
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index c8251f9..593c247 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -474,7 +474,9 @@
     private class SaveBookmarkRunnable implements Runnable {
         // FIXME: This should be an async task.
         private Message mMessage;
-        public SaveBookmarkRunnable(Message msg) {
+        private Context mContext;
+        public SaveBookmarkRunnable(Context ctx, Message msg) {
+            mContext = ctx;
             mMessage = msg;
         }
         public void run() {
@@ -494,7 +496,7 @@
                 Bookmarks.addBookmark(AddBookmarkPage.this, false, url,
                         title, thumbnail, true, mCurrentFolder);
                 if (touchIconUrl != null) {
-                    new DownloadTouchIcon(AddBookmarkPage.this, cr, url).execute(mTouchIconUrl);
+                    new DownloadTouchIcon(mContext, cr, url).execute(mTouchIconUrl);
                 }
                 mMessage.arg1 = 1;
             } catch (IllegalStateException e) {
@@ -600,7 +602,7 @@
             Message msg = Message.obtain(mHandler, SAVE_BOOKMARK);
             msg.setData(bundle);
             // Start a new thread so as to not slow down the UI
-            Thread t = new Thread(new SaveBookmarkRunnable(msg));
+            Thread t = new Thread(new SaveBookmarkRunnable(getApplicationContext(), msg));
             t.start();
             setResult(RESULT_OK);
             LogTag.logBookmarkAdded(url, "bookmarkview");
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index f371e24..cba16a0 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -162,7 +162,8 @@
     // 20 -> 21 Added touch_icon
     // 21 -> 22 Remove "clientid"
     // 22 -> 23 Added user_entered
-    private static final int DATABASE_VERSION = 23;
+    // 23 -> 24 Url not allowed to be null anymore.
+    private static final int DATABASE_VERSION = 24;
 
     // Regular expression which matches http://, followed by some stuff, followed by
     // optionally a trailing slash, all matched as separate groups.
@@ -245,7 +246,7 @@
             db.execSQL("CREATE TABLE bookmarks (" +
                     "_id INTEGER PRIMARY KEY," +
                     "title TEXT," +
-                    "url TEXT," +
+                    "url TEXT NOT NULL," +
                     "visits INTEGER," +
                     "date LONG," +
                     "created LONG," +
@@ -297,6 +298,27 @@
             }
             if (oldVersion < 23) {
                 db.execSQL("ALTER TABLE bookmarks ADD COLUMN user_entered INTEGER;");
+            }
+            if (oldVersion < 24) {
+                /* SQLite does not support ALTER COLUMN, hence the lengthy code. */
+                db.execSQL("DELETE FROM bookmarks WHERE url IS NULL;");
+                db.execSQL("ALTER TABLE bookmarks RENAME TO bookmarks_temp;");
+                db.execSQL("CREATE TABLE bookmarks (" +
+                        "_id INTEGER PRIMARY KEY," +
+                        "title TEXT," +
+                        "url TEXT NOT NULL," +
+                        "visits INTEGER," +
+                        "date LONG," +
+                        "created LONG," +
+                        "description TEXT," +
+                        "bookmark INTEGER," +
+                        "favicon BLOB DEFAULT NULL," +
+                        "thumbnail BLOB DEFAULT NULL," +
+                        "touch_icon BLOB DEFAULT NULL," +
+                        "user_entered INTEGER" +
+                        ");");
+                db.execSQL("INSERT INTO bookmarks SELECT * FROM bookmarks_temp;");
+                db.execSQL("DROP TABLE bookmarks_temp;");
             } else {
                 db.execSQL("DROP TABLE IF EXISTS bookmarks");
                 db.execSQL("DROP TABLE IF EXISTS searches");
diff --git a/src/com/android/browser/DownloadTouchIcon.java b/src/com/android/browser/DownloadTouchIcon.java
index 7bb93dc..992284f 100644
--- a/src/com/android/browser/DownloadTouchIcon.java
+++ b/src/com/android/browser/DownloadTouchIcon.java
@@ -23,10 +23,9 @@
 import org.apache.http.client.params.HttpClientParams;
 import org.apache.http.conn.params.ConnRouteParams;
 
-import android.app.Activity;
 import android.content.ContentResolver;
-import android.content.ContentUris;
 import android.content.ContentValues;
+import android.content.Context;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -35,7 +34,6 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Message;
-import android.provider.BrowserContract;
 import android.provider.BrowserContract.Images;
 import android.webkit.WebView;
 
@@ -51,7 +49,7 @@
     private final String mUserAgent; // Sites may serve a different icon to different UAs
     private Message mMessage;
 
-    private final Activity mActivity;
+    private final Context mContext;
     /* package */ Tab mTab;
 
     /**
@@ -59,9 +57,9 @@
      * the originalUrl so we take account of redirects. Used when the user
      * bookmarks a page from outside the bookmarks activity.
      */
-    public DownloadTouchIcon(Tab tab, BrowserActivity activity, ContentResolver cr, WebView view) {
+    public DownloadTouchIcon(Tab tab, Context ctx, ContentResolver cr, WebView view) {
         mTab = tab;
-        mActivity = activity;
+        mContext = ctx;
         mContentResolver = cr;
         // Store these in case they change.
         mOriginalUrl = view.getOriginalUrl();
@@ -76,9 +74,9 @@
      * TODO: Would be nice to set the user agent here so that there is no
      * potential for the three different ctors here to return different icons.
      */
-    public DownloadTouchIcon(AddBookmarkPage activity, ContentResolver cr, String url) {
+    public DownloadTouchIcon(Context ctx, ContentResolver cr, String url) {
         mTab = null;
-        mActivity = activity;
+        mContext = ctx;
         mContentResolver = cr;
         mOriginalUrl = null;
         mUrl = url;
@@ -90,9 +88,9 @@
      * the passed Message's data bundle with the key "touchIcon" and then send
      * the message.
      */
-    public DownloadTouchIcon(BrowserActivity activity, Message msg, String userAgent) {
+    public DownloadTouchIcon(Context context, Message msg, String userAgent) {
         mMessage = msg;
-        mActivity = activity;
+        mContext = context;
         mContentResolver = null;
         mOriginalUrl = null;
         mUrl = null;
@@ -112,7 +110,7 @@
 
         if (inDatabase || mMessage != null) {
             AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent);
-            HttpHost httpHost = Proxy.getPreferredHttpHost(mActivity, url);
+            HttpHost httpHost = Proxy.getPreferredHttpHost(mContext, url);
             if (httpHost != null) {
                 ConnRouteParams.setDefaultProxy(client.getParams(), httpHost);
             }
diff --git a/src/com/android/browser/TabScrollView.java b/src/com/android/browser/TabScrollView.java
index dc21cb6..0d85920 100644
--- a/src/com/android/browser/TabScrollView.java
+++ b/src/com/android/browser/TabScrollView.java
@@ -1,26 +1,31 @@
 /*
  * Copyright (C) 2010 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
  */
 
 package com.android.browser;
 
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.animation.AccelerateInterpolator;
 import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
 
@@ -34,6 +39,7 @@
     private int mSelected;
     private Drawable mArrowLeft;
     private Drawable mArrowRight;
+    private int mAnimationDuration;
 
     /**
      * @param context
@@ -63,16 +69,20 @@
     }
 
     private void init(Context ctx) {
-        mBrowserActivity = (BrowserActivity)ctx;
+        mBrowserActivity = (BrowserActivity) ctx;
+        mAnimationDuration = ctx.getResources().getInteger(
+                R.integer.tab_animation_duration);
         setHorizontalScrollBarEnabled(false);
         mContentView = new LinearLayout(mBrowserActivity);
         mContentView.setOrientation(LinearLayout.HORIZONTAL);
-        mContentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
-                LayoutParams.MATCH_PARENT));
+        mContentView.setLayoutParams(
+                new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
         addView(mContentView);
         mSelected = -1;
         mArrowLeft = ctx.getResources().getDrawable(R.drawable.ic_arrow_left);
         mArrowRight = ctx.getResources().getDrawable(R.drawable.ic_arrow_right);
+        // prevent ProGuard from removing the property methods
+        setScroll(getScroll());
     }
 
     @Override
@@ -112,6 +122,7 @@
 
     void addTab(View tab) {
         mContentView.addView(tab);
+        animateIn(tab);
         tab.setActivated(false);
     }
 
@@ -122,10 +133,10 @@
         } else if (ix < mSelected) {
             mSelected--;
         }
-        mContentView.removeView(tab);
+        animateOut(tab);
     }
 
-    void ensureChildVisible(View child) {
+    private void ensureChildVisible(View child) {
         if (child != null) {
             int childl = child.getLeft();
             int childr = childl + child.getWidth();
@@ -133,10 +144,10 @@
             int viewr = viewl + getWidth();
             if (childl < viewl) {
                 // need scrolling to left
-                scrollTo(childl, 0);
+                animateScroll(childl);
             } else if (childr > viewr) {
                 // need scrolling to right
-                scrollTo(childr - viewr + viewl, 0);
+                animateScroll(childr - viewr + viewl);
             }
         }
     }
@@ -159,4 +170,60 @@
         }
     }
 
+    private void animateIn(View tab) {
+        ObjectAnimator animator = new ObjectAnimator<PropertyValuesHolder>(
+                mAnimationDuration, tab,
+                new PropertyValuesHolder<Integer>("TranslationX", 500, 0));
+        animator.start();
+    }
+
+    private void animateOut(final View tab) {
+        ObjectAnimator animator = new ObjectAnimator<PropertyValuesHolder>(
+                mAnimationDuration, tab,
+                new PropertyValuesHolder<Integer>("TranslationX", 0,
+                        getScrollX() - tab.getRight()));
+        animator.addListener(new AnimatorListener() {
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mContentView.removeView(tab);
+            }
+
+            @Override
+            public void onAnimationRepeat(Animator animation) {
+            }
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+            }
+        });
+        animator.setInterpolator(new AccelerateInterpolator());
+        animator.start();
+    }
+
+    private void animateScroll(int newscroll) {
+        ObjectAnimator animator = new ObjectAnimator<PropertyValuesHolder>(
+                mAnimationDuration, this,
+                new PropertyValuesHolder<Integer>("scroll", getScrollX(), newscroll));
+        animator.start();
+    }
+
+    /**
+     * required for animation
+     */
+    public void setScroll(int newscroll) {
+        scrollTo(newscroll, getScrollY());
+    }
+
+    /**
+     * required for animation
+     */
+    public int getScroll() {
+        return getScrollX();
+    }
+
 }
diff --git a/src/com/android/browser/UrlInputView.java b/src/com/android/browser/UrlInputView.java
index 8662f55..daf879f 100644
--- a/src/com/android/browser/UrlInputView.java
+++ b/src/com/android/browser/UrlInputView.java
@@ -88,11 +88,16 @@
     @Override
     public void showDropDown() {
         int width = mContainer.getWidth();
-        if ((mAdapter.getLeftCount() == 0) || (mAdapter.getRightCount() == 0)) {
+        if (mLandscape && ((mAdapter.getLeftCount() == 0) ||
+                (mAdapter.getRightCount() == 0))) {
             width = width / 2;
         }
-        setDropDownWidth(width);
-        setDropDownHorizontalOffset(-getLeft());
+        if (width != getDropDownWidth()) {
+            setDropDownWidth(width);
+        }
+        if (getLeft() != -getDropDownHorizontalOffset()) {
+            setDropDownHorizontalOffset(-getLeft());
+        }
         mAdapter.setLandscapeMode(mLandscape);
         super.showDropDown();
     }