tabbed title bar work
   bug # http://b/issue?id=2712871

  added tab bar
  added tab status callbacks to Tab and TabControl
  added compact progress/stop/refresh button
  added UrlInputView for auto-complete suggestions
  modified BrowserProvider for url input suggestions
  modified BrowserActivity to use TitleBarXLarge

Change-Id: I62db2be5b89f4c4f27c09dbc6fee7b3b0d5e91b5
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index ad82fd0..8afd3b4 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -121,9 +121,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Vector;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.Vector;
 
 public class BrowserActivity extends Activity
     implements View.OnCreateContextMenuListener, DownloadListener {
@@ -214,8 +214,12 @@
                 & Configuration.SCREENLAYOUT_SIZE_MASK)
                 == Configuration.SCREENLAYOUT_SIZE_XLARGE;
 
+        // Create the tab control and our initial tab
+        mTabControl = new TabControl(this);
+
+
         if (mXLargeScreenSize) {
-            mTitleBar = new TitleBarXLarge(this);
+            mTitleBar = new TitleBarXLarge(this, mTabControl);
             LinearLayout layout = (LinearLayout) mBrowserFrameLayout.
                     findViewById(R.id.vertical_layout);
             layout.addView(mTitleBar, 0, new LinearLayout.LayoutParams(
@@ -230,8 +234,6 @@
             mFakeTitleBar = new TitleBar(this);
         }
 
-        // Create the tab control and our initial tab
-        mTabControl = new TabControl(this);
 
         // Open the icon database and retain all the bookmark urls for favicons
         retainIconsOnStartup();
@@ -851,6 +853,12 @@
         return true;
     }
 
+    private void rebuildTitleBar() {
+        if (mXLargeScreenSize) {
+            ((TitleBarXLarge) mTitleBar).rebuildLayout();
+        }
+    }
+
     private void showFakeTitleBar() {
         if (mXLargeScreenSize) return;
         if (mFakeTitleBar.getParent() == null && mActiveTabsPage == null
@@ -1060,6 +1068,7 @@
             showHttpAuthentication(mHttpAuthHandler, null, null, title,
                     name, password, focusId);
         }
+        rebuildTitleBar();
     }
 
     @Override
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index 320c438..6af14e8 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -23,16 +23,6 @@
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
@@ -53,6 +43,7 @@
 import android.widget.GridView;
 import android.widget.ListView;
 import android.widget.Toast;
+import android.widget.AdapterView.OnItemClickListener;
 
 /*package*/ enum BookmarkViewMode { NONE, GRID, LIST }
 /**
@@ -269,13 +260,14 @@
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... unused) {
-                BrowserBookmarksAdapter adapter = new BrowserBookmarksAdapter(
-                        BrowserBookmarksPage.this,
-                        url,
-                        title,
-                        thumbnail,
-                        createShortcut,
-                        mostVisited);
+                BrowserBookmarksAdapter adapter =
+                    new BrowserBookmarksAdapter(
+                            BrowserBookmarksPage.this,
+                            url,
+                            title,
+                            thumbnail,
+                            createShortcut,
+                            mostVisited);
                 mHandler.obtainMessage(ADAPTER_CREATED, adapter).sendToTarget();
                 return null;
             }
@@ -352,7 +344,7 @@
                 }
                 listView.setDrawSelectorOnTop(false);
                 listView.setVerticalScrollBarEnabled(true);
-                listView.setOnItemClickListener(mListener);
+                listView.setOnItemClickListener(mListListener);
                 if (mMostVisited) {
                     listView.setEmptyView(mEmptyView);
                 }
@@ -416,7 +408,29 @@
         }
     };
 
-    private AdapterView.OnItemClickListener mListener = new AdapterView.OnItemClickListener() {
+    private OnItemClickListener mListener = new OnItemClickListener() {
+        public void onItemClick(AdapterView parent, View v, int position, long id) {
+            // It is possible that the view has been canceled when we get to
+            // this point as back has a higher priority
+            if (mCanceled) {
+                android.util.Log.e(LOGTAG, "item clicked when dismissing");
+                return;
+            }
+            if (!mCreateShortcut) {
+                if (0 == position && !mMostVisited) {
+                    // XXX: Work-around for a framework issue.
+                    mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE);
+                } else {
+                    loadUrl(position);
+                }
+            } else {
+                setResultToParent(RESULT_OK, createShortcutIntent(position));
+                finish();
+            }
+        }
+    };
+
+    private OnItemClickListener mListListener = new OnItemClickListener() {
         public void onItemClick(AdapterView parent, View v, int position, long id) {
             // It is possible that the view has been canceled when we get to
             // this point as back has a higher priority
@@ -658,4 +672,5 @@
                     resultCode, data);
         }
     }
+
 }
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index 1eec52b..7064180 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -29,8 +29,7 @@
 import android.content.SharedPreferences;
 import android.content.UriMatcher;
 import android.content.SharedPreferences.Editor;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
 import android.database.AbstractCursor;
 import android.database.ContentObserver;
 import android.database.Cursor;
@@ -47,8 +46,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Patterns;
-import android.util.TypedValue;
-
 
 import java.io.File;
 import java.io.FilenameFilter;
@@ -91,6 +88,17 @@
     private static final int SUGGEST_COLUMN_QUERY_ID = 8;
     private static final int SUGGEST_COLUMN_INTENT_EXTRA_DATA = 9;
 
+    // how many suggestions will be shown in dropdown
+    // 0..SHORT: filled by browser db
+    private static final int MAX_SUGGEST_SHORT_SMALL = 3;
+    // SHORT..LONG: filled by search suggestions
+    private static final int MAX_SUGGEST_LONG_SMALL = 6;
+
+    // large screen size shows more
+    private static final int MAX_SUGGEST_SHORT_LARGE = 6;
+    private static final int MAX_SUGGEST_LONG_LARGE = 9;
+
+
     // shared suggestion columns
     private static final String[] COLUMNS = new String[] {
             "_id",
@@ -104,10 +112,6 @@
             SearchManager.SUGGEST_COLUMN_QUERY,
             SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA};
 
-    private static final int MAX_SUGGESTION_SHORT_ENTRIES = 3;
-    private static final int MAX_SUGGESTION_LONG_ENTRIES = 6;
-    private static final String MAX_SUGGESTION_LONG_ENTRIES_STRING =
-            Integer.valueOf(MAX_SUGGESTION_LONG_ENTRIES).toString();
 
     // make sure that these match the index of TABLE_NAMES
     private static final int URI_MATCH_BOOKMARKS = 0;
@@ -167,6 +171,9 @@
 
     private SearchManager mSearchManager;
 
+    private int mMaxSuggestionShortSize;
+    private int mMaxSuggestionLongSize;
+
     public BrowserProvider() {
     }
 
@@ -350,6 +357,20 @@
     @Override
     public boolean onCreate() {
         final Context context = getContext();
+        boolean xlargeScreenSize = (context.getResources().getConfiguration().screenLayout
+                & Configuration.SCREENLAYOUT_SIZE_MASK)
+                == Configuration.SCREENLAYOUT_SIZE_XLARGE;
+        boolean isPortrait = (context.getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_PORTRAIT);
+
+
+        if (xlargeScreenSize && isPortrait) {
+            mMaxSuggestionLongSize = MAX_SUGGEST_LONG_LARGE;
+            mMaxSuggestionShortSize = MAX_SUGGEST_SHORT_LARGE;
+        } else {
+            mMaxSuggestionLongSize = MAX_SUGGEST_LONG_SMALL;
+            mMaxSuggestionShortSize = MAX_SUGGEST_SHORT_SMALL;
+        }
         mOpenHelper = new DatabaseHelper(context);
         mBackupManager = new BackupManager(context);
         // we added "picasa web album" into default bookmarks for version 19.
@@ -467,8 +488,8 @@
             mSuggestCursor = sc;
             mHistoryCount = hc != null ? hc.getCount() : 0;
             mSuggestionCount = sc != null ? sc.getCount() : 0;
-            if (mSuggestionCount > (MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount)) {
-                mSuggestionCount = MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount;
+            if (mSuggestionCount > (mMaxSuggestionLongSize - mHistoryCount)) {
+                mSuggestionCount = mMaxSuggestionLongSize - mHistoryCount;
             }
             mString = string;
             mIncludeWebSearch = string.length() > 0;
@@ -682,6 +703,7 @@
         }
 
         // TODO Temporary change, finalize after jq's changes go in
+        @Override
         public void deactivate() {
             if (mHistoryCursor != null) {
                 mHistoryCursor.deactivate();
@@ -692,12 +714,14 @@
             super.deactivate();
         }
 
+        @Override
         public boolean requery() {
             return (mHistoryCursor != null ? mHistoryCursor.requery() : false) |
                     (mSuggestCursor != null ? mSuggestCursor.requery() : false);
         }
 
         // TODO Temporary change, finalize after jq's changes go in
+        @Override
         public void close() {
             super.close();
             if (mHistoryCursor != null) {
@@ -762,12 +786,15 @@
         public ResultsCursor(ArrayList<String> results) {
             mResults = results;
         }
+        @Override
         public int getCount() { return mResults.size(); }
 
+        @Override
         public String[] getColumnNames() {
             return RESULTS_COLUMNS;
         }
 
+        @Override
         public String getString(int column) {
             switch (column) {
                 case RESULT_ACTION_ID:
@@ -789,24 +816,30 @@
                     return null;
             }
         }
+        @Override
         public short getShort(int column) {
             throw new UnsupportedOperationException();
         }
+        @Override
         public int getInt(int column) {
             throw new UnsupportedOperationException();
         }
+        @Override
         public long getLong(int column) {
             if ((mPos != -1) && column == 0) {
                 return mPos;        // use row# as the _id
             }
             throw new UnsupportedOperationException();
         }
+        @Override
         public float getFloat(int column) {
             throw new UnsupportedOperationException();
         }
+        @Override
         public double getDouble(int column) {
             throw new UnsupportedOperationException();
         }
+        @Override
         public boolean isNull(int column) {
             throw new UnsupportedOperationException();
         }
@@ -868,7 +901,7 @@
 
             Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS],
                     SUGGEST_PROJECTION, suggestSelection, myArgs, null, null,
-                    ORDER_BY, MAX_SUGGESTION_LONG_ENTRIES_STRING);
+                    ORDER_BY, Integer.toString(mMaxSuggestionLongSize));
 
             if (match == URI_MATCH_BOOKMARKS_SUGGEST
                     || Patterns.WEB_URL.matcher(selectionArgs[0]).matches()) {
@@ -877,7 +910,7 @@
                 // get Google suggest if there is still space in the list
                 if (myArgs != null && myArgs.length > 1
                         && mSearchableInfo != null
-                        && c.getCount() < (MAX_SUGGESTION_SHORT_ENTRIES - 1)) {
+                        && c.getCount() < (mMaxSuggestionShortSize - 1)) {
                     Cursor sc = mSearchManager.getSuggestions(mSearchableInfo, selectionArgs[0]);
                     return new MySuggestionCursor(c, sc, selectionArgs[0]);
                 }
@@ -1120,4 +1153,10 @@
         }
     }
 
+    public static Cursor getBookmarksSuggestions(ContentResolver cr, String constraint) {
+        Uri uri = Uri.parse("content://browser/" + SearchManager.SUGGEST_URI_PATH_QUERY);
+        return cr.query(uri, SUGGEST_PROJECTION, SUGGEST_SELECTION,
+            new String[] { constraint }, ORDER_BY);
+    }
+
 }
diff --git a/src/com/android/browser/CircularProgressView.java b/src/com/android/browser/CircularProgressView.java
new file mode 100644
index 0000000..48f293a
--- /dev/null
+++ b/src/com/android/browser/CircularProgressView.java
@@ -0,0 +1,140 @@
+
+/*
+ * 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
+ *
+ *      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.
+ */
+package com.android.browser;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+/**
+ *
+ */
+public class CircularProgressView extends ImageButton {
+
+    private static final int[] ALPHAS = {
+       64, 96, 128, 160, 192, 192, 160, 128, 96, 64
+    };
+
+    // 100 ms delay between frames, 10fps
+    private static int ALPHA_REFRESH_DELAY = 100;
+
+    private int     mEndAngle;
+    private int     mProgress;
+    private Paint   mPaint;
+    private int     mAlpha;
+    private boolean mAnimated;
+    private RectF   mRect;
+    private int     mMaxProgress;
+
+    /**
+     * @param context
+     * @param attrs
+     * @param defStyle
+     */
+    public CircularProgressView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(context);
+    }
+
+    /**
+     * @param context
+     * @param attrs
+     */
+    public CircularProgressView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    /**
+     * @param context
+     */
+    public CircularProgressView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    private void init(Context ctx) {
+        mEndAngle = 0;
+        mProgress = 0;
+        mMaxProgress = 100;
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setColor(Color.BLACK);
+        mRect = new RectF();
+    }
+
+    void setMaxProgress(int max) {
+        mMaxProgress = max;
+    }
+
+    private synchronized boolean isAnimated() {
+        return mAnimated;
+    }
+
+    private synchronized void setAnimated(boolean animated) {
+        mAnimated = animated;
+    }
+
+    void setProgress(int progress) {
+        mProgress = progress;
+        mEndAngle = 360 * progress / mMaxProgress;
+        invalidate();
+        if (!isAnimated() && (progress > 0) && (progress < mMaxProgress)) {
+            setAnimated(true);
+            mAlpha = 0;
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    if (isAnimated()) {
+                        mAlpha = (mAlpha + 1) % ALPHAS.length;
+                        mPaint.setAlpha(ALPHAS[mAlpha]);
+                        invalidate();
+                        postDelayed(this, ALPHA_REFRESH_DELAY);
+                    }
+                }
+            });
+        } else if ((progress <= 0) || (progress >= mMaxProgress))  {
+            setAnimated(false);
+        }
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        int w = getWidth();
+        int h = getHeight();
+        float cx = w * 0.5f;
+        float cy = h * 0.5f;
+        mRect.set(0, 0, w, h);
+        if ((mProgress > 0) && (mProgress < mMaxProgress)) {
+            Path p = new Path();
+            p.moveTo(cx, cy);
+            p.lineTo(cx, 0);
+            p.arcTo(mRect, 270, mEndAngle);
+            p.lineTo(cx, cy);
+            int state = canvas.save();
+            canvas.drawPath(p, mPaint);
+            canvas.restoreToCount(state);
+        }
+        super.onDraw(canvas);
+    }
+
+}
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 6b74a6c..1d9482b 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -16,21 +16,13 @@
 
 package com.android.browser;
 
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Vector;
-
 import android.app.AlertDialog;
 import android.app.SearchManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
 import android.content.Intent;
+import android.content.DialogInterface.OnCancelListener;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
@@ -71,8 +63,17 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.browser.TabControl.TabChangeListener;
 import com.android.common.speech.LoggingEvents;
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Vector;
+
 /**
  * Class for maintaining Tabs with a main WebView and a subwindow.
  */
@@ -502,6 +503,9 @@
             if (mInForeground) {
                 mActivity.onPageStarted(view, url, favicon);
             }
+            if (getTabChangeListener() != null) {
+                getTabChangeListener().onPageStarted(Tab.this);
+            }
         }
 
         @Override
@@ -523,6 +527,9 @@
             if (mInForeground) {
                 mActivity.onPageFinished(view, url);
             }
+            if (getTabChangeListener() != null) {
+                getTabChangeListener().onPageFinished(Tab.this);
+            }
         }
 
         // return true if want to hijack the url to let another app to handle it
@@ -672,6 +679,7 @@
             final ContentResolver cr = mActivity.getContentResolver();
             final String newUrl = url;
             new AsyncTask<Void, Void, Void>() {
+                @Override
                 protected Void doInBackground(Void... unused) {
                     Browser.updateVisitedHistory(cr, newUrl, true);
                     return null;
@@ -948,6 +956,9 @@
             if (mInForeground) {
                 mActivity.onProgressChanged(view, newProgress);
             }
+            if (getTabChangeListener() != null) {
+                getTabChangeListener().onProgress(Tab.this, newProgress);
+            }
         }
 
         @Override
@@ -957,11 +968,16 @@
                 // here, if url is null, we want to reset the title
                 mActivity.setUrlTitle(pageUrl, title);
             }
+            TabChangeListener tcl = getTabChangeListener();
+            if (tcl != null) {
+                tcl.onUrlAndTitle(Tab.this, pageUrl,title);
+            }
             if (pageUrl == null || pageUrl.length()
                     >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
                 return;
             }
             new AsyncTask<Void, Void, Void>() {
+                @Override
                 protected Void doInBackground(Void... unused) {
                     // See if we can find the current url in our history
                     // database and add the new title to it.
@@ -1020,6 +1036,9 @@
             if (mInForeground) {
                 mActivity.setFavicon(icon);
             }
+            if (getTabChangeListener() != null) {
+                getTabChangeListener().onFavicon(Tab.this, icon);
+            }
         }
 
         @Override
@@ -1208,10 +1227,12 @@
         @Override
         public void getVisitedHistory(final ValueCallback<String[]> callback) {
             AsyncTask<Void, Void, String[]> task = new AsyncTask<Void, Void, String[]>() {
+                @Override
                 public String[] doInBackground(Void... unused) {
                     return Browser.getVisitedHistory(mActivity
                             .getContentResolver());
                 }
+                @Override
                 public void onPostExecute(String[] result) {
                     callback.onReceiveValue(result);
                 };
@@ -1990,4 +2011,13 @@
         LinearLayout parent = (LinearLayout) dialog.getParent();
         if (parent != null) parent.removeView(dialog);
     }
+
+    /**
+     * always get the TabChangeListener form the tab control
+     * @return the TabControl change listener
+     */
+    private TabChangeListener getTabChangeListener() {
+        return mActivity.getTabControl().getTabChangeListener();
+    }
+
 }
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index afd4ea8..0fbdd7a 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -132,7 +132,7 @@
     int getCurrentIndex() {
         return mCurrentTab;
     }
-    
+
     /**
      * Given a Tab, find it's index
      * @param Tab to find
@@ -167,6 +167,9 @@
         mTabs.add(t);
         // Initially put the tab in the background.
         t.putInBackground();
+        if (mTabChangeListener != null) {
+            mTabChangeListener.onNewTab(t);
+        }
         return t;
     }
 
@@ -231,6 +234,9 @@
 
         // Remove it from the queue of viewed tabs.
         mTabQueue.remove(t);
+        if (mTabChangeListener != null) {
+            mTabChangeListener.onRemoveTab(t);
+        }
         return true;
     }
 
@@ -617,6 +623,47 @@
                 mainView.loadUrl(BrowserSettings.getInstance().getHomePage());
             }
         }
+        if (mTabChangeListener != null) {
+            mTabChangeListener.onCurrentTab(newTab);
+        }
         return true;
     }
+
+    interface TabChangeListener {
+
+        public void onNewTab(Tab tab);
+
+        public void onRemoveTab(Tab tab);
+
+        public void onCurrentTab(Tab tab);
+
+        public void onProgress(Tab tab, int progress);
+
+        public void onUrlAndTitle(Tab tab, String url, String title);
+
+        public void onFavicon(Tab tab, Bitmap favicon);
+
+        public void onPageStarted(Tab tab);
+
+        public void onPageFinished(Tab tab);
+
+    }
+
+    private TabChangeListener mTabChangeListener;
+
+    /**
+     * register the TabChangeListener with the tab control
+     * @param listener
+     */
+    void setOnTabChangeListener(TabChangeListener listener) {
+        mTabChangeListener = listener;
+    }
+
+    /**
+     * get the current TabChangeListener (used by the tabs)
+     */
+    TabChangeListener getTabChangeListener() {
+        return mTabChangeListener;
+    }
+
 }
diff --git a/src/com/android/browser/TabScrollView.java b/src/com/android/browser/TabScrollView.java
new file mode 100644
index 0000000..6d8b91b
--- /dev/null
+++ b/src/com/android/browser/TabScrollView.java
@@ -0,0 +1,136 @@
+/*
+ * 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
+ *
+ *      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.
+ */
+
+package com.android.browser;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.HorizontalScrollView;
+import android.widget.LinearLayout;
+
+/**
+ * custom view for displaying tabs in the tabbed title bar
+ */
+public class TabScrollView extends HorizontalScrollView {
+
+    private Context mContext;
+
+    private LinearLayout mContentView;
+
+    private int mSelected;
+
+    /**
+     * @param context
+     * @param attrs
+     * @param defStyle
+     */
+    public TabScrollView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(context);
+    }
+
+    /**
+     * @param context
+     * @param attrs
+     */
+    public TabScrollView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    /**
+     * @param context
+     */
+    public TabScrollView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    private void init(Context ctx) {
+        mContext = ctx;
+        setHorizontalScrollBarEnabled(false);
+        mContentView = new LinearLayout(mContext);
+        mContentView.setOrientation(LinearLayout.HORIZONTAL);
+        mContentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT));
+        addView(mContentView);
+        mSelected = -1;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        ensureChildVisible(getSelectedTab());
+    }
+
+    void setSelectedTab(int position) {
+        View v = getSelectedTab();
+        if (v != null) {
+            v.setSelected(false);
+        }
+        mSelected = position;
+        v = getSelectedTab();
+        if (v != null) {
+            v.setSelected(true);
+        }
+        requestLayout();
+    }
+
+    View getSelectedTab() {
+        if ((mSelected >= 0) && (mSelected < mContentView.getChildCount())) {
+            return mContentView.getChildAt(mSelected);
+        } else {
+            return null;
+        }
+    }
+
+    void clearTabs() {
+        mContentView.removeAllViews();
+    }
+
+    void addTab(View tab) {
+        mContentView.addView(tab);
+        tab.setSelected(false);
+    }
+
+    void addTab(View tab, int pos) {
+        mContentView.addView(tab, pos);
+        tab.setSelected(false);
+    }
+
+    void removeTab(View tab) {
+        mContentView.removeView(tab);
+    }
+
+    void ensureChildVisible(View child) {
+        if (child != null) {
+            int childl = child.getLeft();
+            int childr = childl + child.getWidth();
+            int viewl = getScrollX();
+            int viewr = viewl + getWidth();
+            if (childl < viewl) {
+                // need scrolling to left
+                scrollTo(childl, 0);
+            } else if (childr > viewr) {
+                // need scrolling to right
+                scrollTo(childr - viewr + viewl, 0);
+            }
+        }
+    }
+
+
+}
diff --git a/src/com/android/browser/TitleBarBase.java b/src/com/android/browser/TitleBarBase.java
index 3d234e8..7016dc0 100644
--- a/src/com/android/browser/TitleBarBase.java
+++ b/src/com/android/browser/TitleBarBase.java
@@ -35,7 +35,7 @@
     protected ImageView mFavicon;
     protected ImageView mLockIcon;
 
-    private Drawable mGenericFavicon;
+    protected Drawable mGenericFavicon;
 
     public TitleBarBase(Context context) {
         super(context, null);
diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java
index 0d799a8..fd6d67b 100644
--- a/src/com/android/browser/TitleBarXLarge.java
+++ b/src/com/android/browser/TitleBarXLarge.java
@@ -16,108 +16,168 @@
 
 package com.android.browser;
 
+import android.app.SearchManager;
 import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.drawable.Animatable;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.PaintDrawable;
 import android.view.ContextMenu;
 import android.view.LayoutInflater;
 import android.view.MenuInflater;
 import android.view.View;
 import android.widget.ImageView;
-import android.widget.ProgressBar;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.android.common.speech.LoggingEvents;
+import com.android.browser.TabControl.TabChangeListener;
+import com.android.browser.UrlInputView.UrlInputListener;
+
+import java.util.HashMap;
+import java.util.Map;
 
 /**
- * This class represents a title bar for a particular "tab" or "window" in the
- * browser.
+ * tabbed title bar for xlarge screen browser
  */
-public class TitleBarXLarge extends TitleBarBase {
-    private Drawable        mCircularProgress;
-    private ProgressBar     mHorizontalProgress;
-    private Drawable        mStopDrawable;
-    private Drawable        mReloadDrawable;
-    private boolean         mInLoad;
-    private BrowserActivity mBrowserActivity;
+public class TitleBarXLarge extends TitleBarBase
+    implements TabChangeListener, UrlInputListener {
 
-    private final View            mBackButton;
-    private final View            mForwardButton;
-    private final View            mStar;
-    private final View            mMenu;
-    private final ImageView       mStopButton;
-    private final TextView        mTitle;
-    private final View            mAllButton;
+    private static final int PROGRESS_MAX = 100;
 
-    public TitleBarXLarge(BrowserActivity context) {
+    private static final int TAB_WIDTH_SELECTED = 400;
+    private static final int TAB_WIDTH_UNSELECTED = 150;
+
+    private BrowserActivity       mBrowserActivity;
+    private Drawable              mStopDrawable;
+    private Drawable              mReloadDrawable;
+    private Drawable              mSelectedBackground;
+    private Drawable              mUnselectedBackground;
+
+    private View                  mBackButton;
+    private View                  mForwardButton;
+    private View                  mStar;
+    private View                  mMenu;
+    private View                  mAllButton;
+    private TabScrollView         mTabs;
+    private View                  mNewButton;
+    private TabControl            mControl;
+    private UrlInputView          mUrlView;
+
+    private boolean               mIsInLandscape;
+    private Map<Tab, TabViewData> mTabMap;
+
+    private float                 mDensityScale;
+
+    public TitleBarXLarge(BrowserActivity context, TabControl tabcontrol) {
         super(context);
-        Resources resources = context.getResources();
-        LayoutInflater factory = LayoutInflater.from(context);
-        factory.inflate(R.layout.title_bar_xlarge, this);
+        mDensityScale = context.getResources().getDisplayMetrics().density;
+        mTabMap = new HashMap<Tab, TabViewData>();
         mBrowserActivity = context;
-
-        mTitle = (TextView) findViewById(R.id.title);
-        mTitle.setCompoundDrawablePadding(5);
-        mTitle.setLongClickable(true);
-
-        mLockIcon = (ImageView) findViewById(R.id.lock);
-        mFavicon = (ImageView) findViewById(R.id.favicon);
-        mStopButton = (ImageView) findViewById(R.id.stop);
-        mStopDrawable = mStopButton.getDrawable();
+        mControl = tabcontrol;
+        Resources resources = context.getResources();
+        mSelectedBackground = resources.getDrawable(R.drawable.tab_selected_bg);
+        mUnselectedBackground = resources.getDrawable(R.drawable.tab_unselected_bg);
+        mStopDrawable = resources.getDrawable(R.drawable.progress_stop);
         mReloadDrawable = resources.getDrawable(R.drawable.ic_reload);
+        rebuildLayout(context, true);
+        // register the tab change listener
+        mControl.setOnTabChangeListener(this);
+    }
 
-        mAllButton = (ImageView) findViewById(R.id.all_btn);
-        mCircularProgress = (Drawable) resources.getDrawable(
-                com.android.internal.R.drawable.search_spinner);
-        DisplayMetrics metrics = resources.getDisplayMetrics();
-        int iconDimension = (int) TypedValue.applyDimension(
-                TypedValue.COMPLEX_UNIT_DIP, 20f, metrics);
-        mCircularProgress.setBounds(0, 0, iconDimension, iconDimension);
-        mHorizontalProgress = (ProgressBar) findViewById(
-                R.id.progress_horizontal);
-        mHorizontalProgress.setProgressDrawable(
-                resources.getDrawable(R.drawable.progress));
+    void rebuildLayout() {
+        rebuildLayout(mBrowserActivity, false);
+    }
 
-        // FIXME: Change enabled states based on whether you can go
+    private void rebuildLayout(Context context, boolean rebuildData) {
+        removeAllViews();
+        LayoutInflater factory = LayoutInflater.from(context);
+        factory.inflate(R.layout.title_bar_tabbed, this);
+
+        mTabs = (TabScrollView) findViewById(R.id.tabs);
+        mNewButton = findViewById(R.id.newtab);
+        mUrlView = (UrlInputView) findViewById(R.id.editurl);
+        mAllButton = findViewById(R.id.all_btn);
+        // TODO: Change enabled states based on whether you can go
         // back/forward.  Probably should be done inside onPageStarted.
         mBackButton = findViewById(R.id.back);
         mForwardButton = findViewById(R.id.forward);
         mStar = findViewById(R.id.star);
         mMenu = findViewById(R.id.menu);
         View.OnClickListener listener = new View.OnClickListener() {
-                public void onClick(View v) {
-                    if (mBackButton == v) {
-                        mBrowserActivity.getTopWindow().goBack();
-                    } else if (mForwardButton == v) {
-                        mBrowserActivity.getTopWindow().goForward();
-                    } else if (mStar == v) {
-                        mBrowserActivity.promptAddOrInstallBookmark();
-                    } else if (mMenu == v) {
-                        mBrowserActivity.openOptionsMenu();
-                    } else if (mStopButton == v) {
-                        if (mInLoad) {
-                            mBrowserActivity.stopLoading();
-                        } else {
-                            mBrowserActivity.getTopWindow().reload();
-                        }
-                    } else if (mTitle == v) {
-                        mBrowserActivity.editUrl();
-                    } else if (mAllButton == v) {
-                        // FIXME: Show the new bookmarks/windows view.
-                        mBrowserActivity.bookmarksOrHistoryPicker(false);
-                    }
+            public void onClick(View v) {
+                if (mBackButton == v) {
+                    mBrowserActivity.getTopWindow().goBack();
+                } else if (mForwardButton == v) {
+                    mBrowserActivity.getTopWindow().goForward();
+                } else if (mStar == v) {
+                    mBrowserActivity.promptAddOrInstallBookmark();
+                } else if (mMenu == v) {
+                    mBrowserActivity.openOptionsMenu();
+                } else if (mAllButton == v) {
+                    // TODO: Show the new bookmarks/windows view.
+                    mBrowserActivity.bookmarksOrHistoryPicker(false);
+                } else if (mNewButton == v) {
+                    mBrowserActivity.openTabToHomePage();
                 }
+            }
         };
         mBackButton.setOnClickListener(listener);
         mForwardButton.setOnClickListener(listener);
         mStar.setOnClickListener(listener);
-        mStopButton.setOnClickListener(listener);
-        mTitle.setOnClickListener(listener);
         mAllButton.setOnClickListener(listener);
         mMenu.setOnClickListener(listener);
+        mNewButton.setOnClickListener(listener);
+
+        mIsInLandscape = mBrowserActivity.getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_LANDSCAPE;
+        mUrlView.setVisibility(mIsInLandscape ? View.GONE : View.VISIBLE);
+        mUrlView.setUrlInputListener(this);
+        buildTabs(rebuildData);
+        // ensure title bar state
+        onCurrentTab(mControl.getCurrentTab());
+    }
+
+    void showUrlEditor(TabViewData tabdata) {
+        mUrlView.setVisibility(View.VISIBLE);
+        if (mIsInLandscape) {
+            mTabs.setVisibility(View.GONE);
+            mUrlView.requestFocus();
+            mUrlView.forceIme();
+        }
+    }
+
+    void hideUrlEditor() {
+        Tab tab = mControl.getCurrentTab();
+        if (mIsInLandscape) {
+            mUrlView.setVisibility(View.GONE);
+            mTabs.setVisibility(View.VISIBLE);
+        } else {
+            // portrait mode
+            mUrlView.setText(tab.getWebView().getUrl());
+        }
+        tab.getWebView().requestFocus();
+    }
+
+
+    // UrlInputListener implementation
+
+    @Override
+    public void onAction(String text) {
+        hideUrlEditor();
+        Intent i = new Intent();
+        i.setAction(Intent.ACTION_SEARCH);
+        i.putExtra(SearchManager.QUERY, text);
+        mBrowserActivity.onNewIntent(i);
+    }
+
+    @Override
+    public void onDismiss() {
+        hideUrlEditor();
     }
 
     @Override
@@ -127,44 +187,321 @@
         mBrowserActivity.onCreateContextMenu(menu, this, null);
     }
 
-    /**
-     * Update the progress, from 0 to 100.
-     */
-    /* package */ void setProgress(int newProgress) {
-        if (newProgress >= mHorizontalProgress.getMax()) {
-            mTitle.setCompoundDrawables(null, null, null, null);
-            ((Animatable) mCircularProgress).stop();
-            mHorizontalProgress.setVisibility(View.GONE);
-            mInLoad = false;
-            mStopButton.setImageDrawable(mReloadDrawable);
-        } else {
-            mHorizontalProgress.setProgress(newProgress);
-            if (!mInLoad && getWindowToken() != null) {
-                // checking the window token lets us be sure that we
-                // are attached to a window before starting the animation,
-                // preventing a potential race condition
-                // (fix for bug http://b/2115736)
-                mTitle.setCompoundDrawables(null, null, mCircularProgress,
-                        null);
-                ((Animatable) mCircularProgress).start();
-                mHorizontalProgress.setVisibility(View.VISIBLE);
-                mInLoad = true;
-                mStopButton.setImageDrawable(mStopDrawable);
-            }
-        }
+    @Override
+    /* package */ void setLock(Drawable d) {
+        // TODO: handle in tab specific callback
+    }
+
+    @Override
+    /* package */ void setFavicon(Bitmap icon) {
+        // this is handled in the tab specific callback
     }
 
     /**
-     * Update the text displayed in the title bar.
-     * @param title String to display.  If null, the loading string will be
-     *      shown.
+     * Update the progress, from 0 to 100.
      */
+    @Override
+    /* package */ void setProgress(int newProgress) {
+        // this is handled in tab specific callback
+    }
+
+    @Override
     /* package */ void setDisplayTitle(String title) {
-        if (title == null) {
-            mTitle.setText(R.string.title_bar_loading);
+        // this is done in tab specific callback
+    }
+
+    private void buildTabs(boolean needsRebuilding) {
+        mTabs.clearTabs();
+        for (int i = 0; i < mControl.getTabCount(); i++) {
+            Tab tab = mControl.getTab(i);
+            TabViewData data = buildTab(needsRebuilding, tab);
+            TabView tv = buildView(data);
+        }
+        mTabs.setSelectedTab(mControl.getCurrentIndex());
+    }
+
+    private TabViewData buildTab(boolean needsRebuilding, Tab tab) {
+        TabViewData data = null;
+        if (needsRebuilding) {
+            data = new TabViewData(tab);
+            mTabMap.put(tab, data);
         } else {
+            data = mTabMap.get(tab);
+        }
+        return data;
+    }
+
+    private TabView buildView(final TabViewData data) {
+        TabView tv = new TabView(mBrowserActivity, data);
+        tv.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mTabs.getSelectedTab() == v) {
+                    showUrlEditor(data);
+                } else {
+                    int ix = mControl.getTabIndex(data.mTab);
+                    mTabs.setSelectedTab(ix);
+                    mBrowserActivity.switchToTab(ix);
+                }
+            }
+        });
+        mTabs.addTab(tv);
+        return tv;
+    }
+
+    /**
+     * the views used in the tab bar
+     */
+    class TabView extends LinearLayout {
+
+        TabViewData          mTabData;
+        View                 mTabContent;
+        TextView             mTitle;
+        ImageView            mIconView;
+        ImageView            mLock;
+        CircularProgressView mStop;
+        ImageView            mClose;
+        boolean              mSelected;
+        boolean              mInLoad;
+
+        /**
+         * @param context
+         */
+        public TabView(Context context, TabViewData tab) {
+            super(context);
+            mTabData = tab;
+            LayoutInflater inflater = LayoutInflater.from(mContext);
+            mTabContent = inflater.inflate(R.layout.tab_title, this);
+            mTitle = (TextView) mTabContent.findViewById(R.id.title);
+            mIconView = (ImageView) mTabContent.findViewById(R.id.favicon);
+            mLock = (ImageView) mTabContent.findViewById(R.id.lock);
+            mStop = (CircularProgressView) mTabContent.findViewById(R.id.stop);
+            mStop.setMaxProgress(PROGRESS_MAX);
+            mClose = (ImageView) mTabContent.findViewById(R.id.close);
+            mClose.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    closeTab();
+                }
+            });
+            mStop.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (mInLoad) {
+                        mBrowserActivity.stopLoading();
+                    } else {
+                        mBrowserActivity.getTopWindow().reload();
+                    }
+                }
+            });
+            mSelected = false;
+            mInLoad = false;
+            // update the status
+            updateFromData();
+        }
+
+        private void updateFromData() {
+            mTabData.mTabView = this;
+            if (mTabData.mUrl != null) {
+                setDisplayTitle(mTabData.mUrl);
+            }
+            if (mTabData.mTitle != null) {
+                setDisplayTitle(mTabData.mTitle);
+            }
+            setProgress(mTabData.mProgress);
+            if (mTabData.mIcon != null) {
+                setFavicon(mTabData.mIcon);
+            }
+            if (mTabData.mLock != null) {
+                setLock(mTabData.mLock);
+            }
+        }
+
+        @Override
+        public void setSelected(boolean selected) {
+            mSelected = selected;
+            mStop.setVisibility(mSelected ? View.VISIBLE : View.GONE);
+            mIconView.setVisibility(mSelected ? View.VISIBLE : View.GONE);
+            super.setSelected(selected);
+            setBackgroundDrawable(selected ? mSelectedBackground
+                    : mUnselectedBackground);
+            setLayoutParams(new LayoutParams(selected ?
+                    (int) (TAB_WIDTH_SELECTED * mDensityScale)
+                    : (int) (TAB_WIDTH_UNSELECTED * mDensityScale),
+                    LayoutParams.WRAP_CONTENT));
+        }
+
+        void setDisplayTitle(String title) {
             mTitle.setText(title);
         }
+
+        void setFavicon(Drawable d) {
+            mIconView.setImageDrawable(d);
+        }
+
+        void setLock(Drawable d) {
+            if (null == d) {
+                mLock.setVisibility(View.GONE);
+            } else {
+                mLock.setImageDrawable(d);
+                mLock.setVisibility(View.VISIBLE);
+            }
+        }
+
+        void setTitleCompoundDrawables(Drawable left, Drawable top,
+                                              Drawable right, Drawable bottom) {
+            mTitle.setCompoundDrawables(left, top, right, bottom);
+        }
+
+        void setProgress(int newProgress) {
+            mStop.setProgress(newProgress);
+            if (newProgress >= PROGRESS_MAX) {
+                mInLoad = false;
+                mStop.setImageDrawable(mReloadDrawable);
+            } else {
+                if (!mInLoad && getWindowToken() != null) {
+                    // checking the window token lets us be sure that we
+                    // are attached to a window before starting the animation,
+                    // preventing a potential race condition
+                    // (fix for bug http://b/2115736)
+                    mInLoad = true;
+                    mStop.setImageDrawable(mStopDrawable);
+                }
+            }
+        }
+
+        private void closeTab() {
+            if (mTabData.mTab == mControl.getCurrentTab()) {
+                mBrowserActivity.closeCurrentWindow();
+            } else {
+                mBrowserActivity.closeTab(mTabData.mTab);
+            }
+        }
+
+    }
+
+    /**
+     * class to store tab state within the title bar
+     */
+    class TabViewData {
+
+        Tab mTab;
+        TabView mTabView;
+        int mProgress;
+        Drawable mIcon;
+        Drawable mLock;
+        String mTitle;
+        String mUrl;
+
+        TabViewData(Tab tab) {
+            mTab = tab;
+        }
+
+        void setUrlAndTitle(String url, String title) {
+            mUrl = url;
+            mTitle = title;
+            if (mTabView != null) {
+                if (title != null) {
+                    mTabView.setDisplayTitle(title);
+                } else if (url != null) {
+                    mTabView.setDisplayTitle(url);
+                }
+            }
+        }
+
+        void setProgress(int newProgress) {
+            mProgress = newProgress;
+            if (mTabView != null) {
+                mTabView.setProgress(mProgress);
+            }
+        }
+
+        void setFavicon(Bitmap icon) {
+            Drawable[] array = new Drawable[3];
+            array[0] = new PaintDrawable(Color.BLACK);
+            array[1] = new PaintDrawable(Color.WHITE);
+            if (icon == null) {
+                array[2] = mGenericFavicon;
+            } else {
+                array[2] = new BitmapDrawable(icon);
+            }
+            LayerDrawable d = new LayerDrawable(array);
+            d.setLayerInset(1, 1, 1, 1, 1);
+            d.setLayerInset(2, 2, 2, 2, 2);
+            mIcon = d;
+            if (mTabView != null) {
+                mTabView.setFavicon(mIcon);
+            }
+        }
+
+    }
+
+    // TabChangeListener implementation
+
+    @Override
+    public void onCurrentTab(Tab tab) {
+        mTabs.setSelectedTab(mControl.getCurrentIndex());
+        TabViewData tvd = mTabMap.get(tab);
+        if (tvd != null) {
+            if (tvd.mUrl != null) {
+                mUrlView.setText(tvd.mUrl);
+            }
+            setProgress(tvd.mProgress);
+        }
+    }
+
+    @Override
+    public void onFavicon(Tab tab, Bitmap favicon) {
+        TabViewData tvd = mTabMap.get(tab);
+        if (tvd != null) {
+            tvd.setFavicon(favicon);
+        }
+    }
+
+    @Override
+    public void onNewTab(Tab tab) {
+        TabViewData tvd = buildTab(true, tab);
+        buildView(tvd);
+    }
+
+    @Override
+    public void onProgress(Tab tab, int progress) {
+        TabViewData tvd = mTabMap.get(tab);
+        if (tvd != null) {
+            tvd.setProgress(progress);
+        }
+        if (tab == mControl.getCurrentTab()) {
+            setProgress(progress);
+        }
+    }
+
+    @Override
+    public void onRemoveTab(Tab tab) {
+        TabViewData tvd = mTabMap.get(tab);
+        TabView tv = tvd.mTabView;
+        if (tv != null) {
+            mTabs.removeTab(tv);
+        }
+        mTabMap.remove(tab);
+    }
+
+    @Override
+    public void onUrlAndTitle(Tab tab, String url, String title) {
+        TabViewData tvd = mTabMap.get(tab);
+        if (tvd != null) {
+            tvd.setUrlAndTitle(url, title);
+        }
+        if ((url != null) && (tab == mControl.getCurrentTab())) {
+            mUrlView.setText(url);
+        }
+    }
+
+    @Override
+    public void onPageFinished(Tab tab) {
+    }
+
+    @Override
+    public void onPageStarted(Tab tab) {
     }
 
 }
diff --git a/src/com/android/browser/UrlInputView.java b/src/com/android/browser/UrlInputView.java
new file mode 100644
index 0000000..3841257
--- /dev/null
+++ b/src/com/android/browser/UrlInputView.java
@@ -0,0 +1,206 @@
+/*
+ * 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
+ *
+ *      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.
+ */
+
+package com.android.browser;
+
+import android.app.SearchManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.AutoCompleteTextView;
+import android.widget.CursorAdapter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.AdapterView.OnItemClickListener;
+
+/**
+ * url/search input view
+ * handling suggestions
+ */
+public class UrlInputView extends AutoCompleteTextView {
+
+    private UrlInputListener   mListener;
+    private InputMethodManager mInputManager;
+    private SuggestionsAdapter mAdapter;
+    private Drawable           mFocusDrawable;
+    private Drawable           mNoFocusDrawable;
+
+
+    public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(context);
+    }
+
+    public UrlInputView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public UrlInputView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    private void init(Context ctx) {
+        mFocusDrawable = ctx.getResources().getDrawable(R.drawable.textfield_stroke);
+        mNoFocusDrawable = ctx.getResources().getDrawable(R.drawable.textfield_nostroke);
+        mInputManager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
+        setOnEditorActionListener(new OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                finishInput(getText().toString());
+                return true;
+            }
+        });
+        setOnFocusChangeListener(new OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View v, boolean hasFocus) {
+                setBackgroundDrawable(hasFocus ? mFocusDrawable : mNoFocusDrawable);
+            }
+        });
+        final ContentResolver cr = mContext.getContentResolver();
+        mAdapter = new SuggestionsAdapter(mContext,
+                BrowserProvider.getBookmarksSuggestions(cr, null));
+        setAdapter(mAdapter);
+        setOnItemClickListener(new OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                String url = mAdapter.getViewString(view);
+                finishInput(url);
+            }
+        });
+        setSelectAllOnFocus(true);
+    }
+
+    public void setUrlInputListener(UrlInputListener listener) {
+        mListener = listener;
+    }
+
+    public void forceIme() {
+        mInputManager.showSoftInput(this, 0);
+    }
+
+    private void finishInput(String url) {
+        this.dismissDropDown();
+        mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
+        if (url == null) {
+            mListener.onDismiss();
+        } else {
+            mListener.onAction(url);
+        }
+
+    }
+
+    @Override
+    public boolean onKeyPreIme(int keyCode, KeyEvent evt) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            // catch back key in order to do slightly more cleanup than usual
+            finishInput(null);
+            return true;
+        }
+        return super.onKeyPreIme(keyCode, evt);
+    }
+
+    interface UrlInputListener {
+
+        public void onDismiss();
+
+        public void onAction(String text);
+
+    }
+
+    /**
+     * adapter used by suggestion dropdown
+     */
+    class SuggestionsAdapter extends CursorAdapter implements Filterable {
+
+        private Cursor          mLastCursor;
+        private ContentResolver mContent;
+        private int             mIndexText1;
+        private int             mIndexText2;
+        private int             mIndexIcon;
+
+        public SuggestionsAdapter(Context context, Cursor c) {
+            super(context, c);
+            mContent = context.getContentResolver();
+            mIndexText1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
+            mIndexText2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
+            mIndexIcon = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
+        }
+
+        public String getViewString(View view) {
+            TextView tv2 = (TextView) view.findViewById(android.R.id.text2);
+            if (tv2.getText().length() > 0) {
+                return tv2.getText().toString();
+            } else {
+                TextView tv1 = (TextView) view.findViewById(android.R.id.text1);
+                return tv1.getText().toString();
+            }
+        }
+
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+            final LayoutInflater inflater = LayoutInflater.from(context);
+            final View view = inflater.inflate(
+                    R.layout.simple_dropdown_item_2line, parent, false);
+            bindView(view, context, cursor);
+            return view;
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            TextView tv1 = (TextView) view.findViewById(android.R.id.text1);
+            TextView tv2 = (TextView) view.findViewById(android.R.id.text2);
+            ImageView ic1 = (ImageView) view.findViewById(R.id.icon1);
+            tv1.setText(cursor.getString(mIndexText1));
+            String url = cursor.getString(mIndexText2);
+            tv2.setText((url != null) ? url : "");
+            // assume an id
+            try {
+                int id = Integer.parseInt(cursor.getString(mIndexIcon));
+                Drawable d = context.getResources().getDrawable(id);
+                ic1.setImageDrawable(d);
+            } catch (NumberFormatException nfx) {
+            }
+        }
+
+        @Override
+        public String convertToString(Cursor cursor) {
+            return cursor.getString(mIndexText1);
+        }
+
+        @Override
+        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+            if (getFilterQueryProvider() != null) {
+                return getFilterQueryProvider().runQuery(constraint);
+            }
+            mLastCursor = BrowserProvider.getBookmarksSuggestions(mContent,
+                    (constraint != null) ? constraint.toString() : null);
+            return mLastCursor;
+        }
+
+    }
+
+}