OSDN Git Service

Handle BACK key at UP in the Browser.
authorGrace Kloba <klobag@google.com>
Fri, 18 Sep 2009 18:48:29 +0000 (11:48 -0700)
committerGrace Kloba <klobag@google.com>
Sun, 20 Sep 2009 17:21:23 +0000 (10:21 -0700)
Remove KeyTracker, use KeyEvent instead.

Remove ImageGrid and ImageAdapter.

src/com/android/browser/ActiveTabsPage.java
src/com/android/browser/BrowserActivity.java
src/com/android/browser/BrowserBookmarksPage.java
src/com/android/browser/ImageAdapter.java [deleted file]
src/com/android/browser/ImageGrid.java [deleted file]
src/com/android/browser/KeyTracker.java [deleted file]

index 2971e09..e589d42 100644 (file)
@@ -68,15 +68,6 @@ public class ActiveTabsPage extends LinearLayout {
         });
     }
 
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
-            if (event.isDown()) return true;
-            mBrowserActivity.removeActiveTabPage(true);
-            return true;
-        }
-        return super.dispatchKeyEvent(event);
-    }
-
     /**
      * Special class to hold the close drawable.  Its sole purpose is to allow
      * the parent to be pressed without being pressed itself.  This way the line
index 1730f9b..15772a5 100644 (file)
@@ -149,8 +149,7 @@ import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 public class BrowserActivity extends Activity
-    implements KeyTracker.OnKeyTracker,
-        View.OnCreateContextMenuListener,
+    implements View.OnCreateContextMenuListener,
         DownloadListener {
 
     /* Define some aliases to make these debugging flags easier to refer to.
@@ -2130,80 +2129,73 @@ public class BrowserActivity extends Activity
         }
     }
 
-    public KeyTracker.State onKeyTracker(int keyCode,
-                                         KeyEvent event,
-                                         KeyTracker.Stage stage,
-                                         int duration) {
-        // if onKeyTracker() is called after activity onStop()
-        // because of accumulated key events,
-        // we should ignore it as browser is not active any more.
-        WebView topWindow = getTopWindow();
-        if (topWindow == null && mCustomView == null)
-            return KeyTracker.State.NOT_TRACKING;
-
-        if (keyCode == KeyEvent.KEYCODE_BACK) {
-            // Check if a custom view is currently showing and, if it is, hide it.
-            if (mCustomView != null) {
-                mWebChromeClient.onHideCustomView();
-                return KeyTracker.State.DONE_TRACKING;
-            }
-            if (stage == KeyTracker.Stage.LONG_REPEAT) {
-                bookmarksOrHistoryPicker(true);
-                return KeyTracker.State.DONE_TRACKING;
-            } else if (stage == KeyTracker.Stage.UP) {
-                // FIXME: Currently, we do not have a notion of the
-                // history picker for the subwindow, but maybe we
-                // should?
-                WebView subwindow = mTabControl.getCurrentSubWindow();
-                if (subwindow != null) {
-                    if (subwindow.canGoBack()) {
-                        subwindow.goBack();
-                    } else {
-                        dismissSubWindow(mTabControl.getCurrentTab());
-                    }
-                } else {
-                    goBackOnePageOrQuit();
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
+        // still down, we don't want to trigger the search. Pretend to consume
+        // the key and do nothing.
+        if (mMenuIsDown) return true;
+
+        switch(keyCode) {
+            case KeyEvent.KEYCODE_MENU:
+                mMenuIsDown = true;
+                break;
+            case KeyEvent.KEYCODE_SPACE:
+                // Browser's hidden shortcut key. Don't call super so that
+                // search won't be triggered.
+                return true;
+            case KeyEvent.KEYCODE_BACK:
+                if (event.getRepeatCount() == 0) {
+                    event.startTracking();
+                    return true;
+                } else if (mCustomView == null && mActiveTabsPage == null
+                        && event.isLongPress()) {
+                    bookmarksOrHistoryPicker(true);
+                    return true;
                 }
-                return KeyTracker.State.DONE_TRACKING;
-            }
-            return KeyTracker.State.KEEP_TRACKING;
+                break;
         }
-        return KeyTracker.State.NOT_TRACKING;
+        return super.onKeyDown(keyCode, event);
     }
 
-    @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_MENU) {
-            mMenuIsDown = true;
-        } else if (mMenuIsDown) {
-            // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
-            // still down, we don't want to trigger the search. Pretend to
-            // consume the key and do nothing.
-            return true;
-        }
-        boolean handled =  mKeyTracker.doKeyDown(keyCode, event);
-        if (!handled) {
-            switch (keyCode) {
-                case KeyEvent.KEYCODE_SPACE:
-                    if (event.isShiftPressed()) {
-                        getTopWindow().pageUp(false);
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch(keyCode) {
+            case KeyEvent.KEYCODE_MENU:
+                mMenuIsDown = false;
+                break;
+            case KeyEvent.KEYCODE_SPACE:
+                if (event.isShiftPressed()) {
+                    getTopWindow().pageUp(false);
+                } else {
+                    getTopWindow().pageDown(false);
+                }
+                return true;
+            case KeyEvent.KEYCODE_BACK:
+                if (event.isTracking() && !event.isCanceled()) {
+                    if (mCustomView != null) {
+                        // if a custom view is showing, hide it
+                        mWebChromeClient.onHideCustomView();
+                    } else if (mActiveTabsPage != null) {
+                        // if tab page is showing, hide it
+                        removeActiveTabPage(true);
                     } else {
-                        getTopWindow().pageDown(false);
+                        WebView subwindow = mTabControl.getCurrentSubWindow();
+                        if (subwindow != null) {
+                            if (subwindow.canGoBack()) {
+                                subwindow.goBack();
+                            } else {
+                                dismissSubWindow(mTabControl.getCurrentTab());
+                            }
+                        } else {
+                            goBackOnePageOrQuit();
+                        }
                     }
-                    handled = true;
-                    break;
-
-                default:
-                    break;
-            }
-        }
-        return handled || super.onKeyDown(keyCode, event);
-    }
-
-    @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_MENU) {
-            mMenuIsDown = false;
+                    return true;
+                }
+                break;
         }
-        return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
+        return super.onKeyUp(keyCode, event);
     }
 
     /* package */ void stopLoading() {
@@ -4244,11 +4236,6 @@ public class BrowserActivity extends Activity
 
     private boolean mMenuIsDown;
 
-    private final KeyTracker mKeyTracker = new KeyTracker(this);
-
-    // As trackball doesn't send repeat down, we have to track it ourselves
-    private boolean mTrackTrackball;
-
     private static boolean mInTrace;
 
     // Performance probe
index c981d7a..b91e322 100644 (file)
@@ -631,13 +631,12 @@ public class BrowserBookmarksPage extends Activity implements
     public void deleteBookmark(int position) {
         mBookmarksAdapter.deleteRow(position);
     }
-    
-    public boolean dispatchKeyEvent(KeyEvent event) {    
-        if (event.getKeyCode() ==  KeyEvent.KEYCODE_BACK && event.isDown()) {
-            setResultToParent(RESULT_CANCELED, null);
-            mCanceled = true;
-        }
-        return super.dispatchKeyEvent(event);
+
+    @Override
+    public void onBackPressed() {
+        setResultToParent(RESULT_CANCELED, null);
+        mCanceled = true;
+        super.onBackPressed();
     }
 
     // This Activity is generally a sub-Activity of CombinedHistoryActivity. In
diff --git a/src/com/android/browser/ImageAdapter.java b/src/com/android/browser/ImageAdapter.java
deleted file mode 100644 (file)
index f95753a..0000000
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2008 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.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.DataSetObserver;
-import android.graphics.Color;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.LayoutInflater;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-
-/**
- * Adapter used by ImageGrid.
- */
-public class ImageAdapter implements ListAdapter {
-    
-    ArrayList<TabControl.Tab> mItems;  // Items shown in the grid
-    private ArrayList<DataSetObserver> mDataObservers; // Data change listeners
-    private Context mContext;  // Context to use to inflate views
-    private boolean mMaxedOut;
-    private ImageGrid mImageGrid;
-    private boolean mIsLive;
-    private int mTabHeight;
-
-    ImageAdapter(Context context, ImageGrid grid, boolean live) {
-        mContext = context;
-        mIsLive = live;
-        mItems = new ArrayList<TabControl.Tab>();
-        mImageGrid = grid;
-        mDataObservers = new ArrayList<DataSetObserver>();
-    }
-
-    void heightChanged(int newHeight) {
-        mTabHeight = newHeight;
-    }
-
-    /**
-     *  Whether the adapter is at its limit, determined by TabControl.MAX_TABS
-     *
-     *  @return True if the number of Tabs represented in this Adapter is at its
-     *          maximum.
-     */
-    public boolean maxedOut() {
-        return mMaxedOut;
-    }
-
-    /**
-     * Clear the internal WebViews and remove their picture listeners.
-     */
-    public void clear() {
-        mItems.clear();
-        notifyObservers();
-    }
-
-    /**
-     * Add a new window web page to the grid
-     * 
-     * @param t The tab to display
-     */
-    public void add(TabControl.Tab t) {
-        if (mMaxedOut) {
-            return;
-        }
-        mItems.add(t);
-        notifyObservers();
-        if (mItems.size() == TabControl.MAX_TABS) {
-            mMaxedOut = true;
-        }
-    }
-    
-    /**
-     * Remove a window from the list. At this point, the window
-     * has already gone. It just needs to be removed from the screen
-     * 
-     * @param index window to remove
-     */
-    public void remove(int index) {
-        if (index >= 0 && index < mItems.size()) {
-            mItems.remove(index);
-            notifyObservers();
-            mMaxedOut = false;
-        }
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.ListAdapter#areAllItemsSelectable()
-     */
-    public boolean areAllItemsEnabled() {
-        return true;
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.ListAdapter#isSelectable(int)
-     */
-    public boolean isEnabled(int position) {
-        if (position >= 0 && position <= mItems.size()) {
-            return true;
-        }
-        return false;
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#getCount()
-     */
-    public int getCount() {
-        // Include the New Window button if we have not reached the tab limit
-        if (!mMaxedOut) {
-            return mItems.size()+1;
-        }
-        return mItems.size();
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#getItem(int)
-     */
-    public Object getItem(int position) {
-        if (!mMaxedOut) {
-            if (0 == position) {
-                return null;
-            }
-            return mItems.get(position);
-        }
-        return mItems.get(position);
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#getItemId(int)
-     */
-    public long getItemId(int position) {
-        return position;
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#getView(int, android.view.View, 
-     * android.view.ViewGroup)
-     */
-    public View getView(int position, View convertView, ViewGroup parent) {
-        View v = null;
-        if (convertView != null) {
-            v = convertView;
-        } else {
-            LayoutInflater factory = LayoutInflater.from(mContext);
-            v = factory.inflate(R.layout.tabitem, null);
-        }
-        FakeWebView img = (FakeWebView) v.findViewById(R.id.icon);
-        ImageView close = (ImageView) v.findViewById(R.id.close);
-        TextView tv = (TextView) v.findViewById(R.id.label);
-
-        // position needs to be in the range of Tab indices.
-        if (!mMaxedOut) {
-            position--;
-        }
-
-        // Create the View for actual tabs
-        if (position != ImageGrid.NEW_TAB) {
-            TabControl.Tab t = mItems.get(position);
-            img.setTab(t);
-            tv.setText(t.getTitle());
-            // Do not put the 'X' if the tab picker isn't "live" (meaning the
-            // user cannot click on a tab)
-            if (!mIsLive) {
-                close.setVisibility(View.GONE);
-            } else {
-                close.setVisibility(View.VISIBLE);
-                final int pos = position;
-                close.setOnClickListener(new View.OnClickListener() {
-                        public void onClick(View v) {
-                            ImageAdapter.this.confirmClose(pos);
-                        }
-                    });
-            }
-        } else {
-            img.setBackgroundColor(Color.BLACK);
-            img.setImageResource(R.drawable.ic_new_window);
-            img.setScaleType(ImageView.ScaleType.CENTER);
-            img.setPadding(0, 0, 0, 34);
-            tv.setText(R.string.new_window);
-            close.setVisibility(View.GONE);
-        }
-        ViewGroup.LayoutParams lp = img.getLayoutParams();
-        if (lp.height != mTabHeight) {
-            lp.height = mTabHeight;
-            img.requestLayout();
-        }
-        return v;
-    }
-
-    /*
-     * Pop a confirmation dialog to the user asking if they want to close this
-     * tab.
-     */
-    private void confirmClose(final int position) {
-        final ImageGrid.Listener l = mImageGrid.getListener();
-        if (l == null) {
-            return;
-        }
-        l.remove(position);
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#registerDataSetObserver(android.database.DataSetObserver)
-     */
-    public void registerDataSetObserver(DataSetObserver observer) {
-        mDataObservers.add(observer);
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#hasStableIds()
-     */
-    public boolean hasStableIds() {
-        return true;
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.Adapter#unregisterDataSetObserver(android.database.DataSetObserver)
-     */
-    public void unregisterDataSetObserver(DataSetObserver observer) {
-        mDataObservers.remove(observer);
-    }
-
-    /**
-     * Notify all the observers that a change has happened.
-     */
-    void notifyObservers() {
-        for (DataSetObserver observer : mDataObservers) {
-            observer.onChanged();
-        }
-    }
-
-    public int getItemViewType(int position) {
-        return 0;
-    }
-
-    public int getViewTypeCount() {
-        return 1;
-    }
-
-    public boolean isEmpty() {
-        return getCount() == 0;
-    }
-}
diff --git a/src/com/android/browser/ImageGrid.java b/src/com/android/browser/ImageGrid.java
deleted file mode 100644 (file)
index 9967f36..0000000
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2008 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.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.KeyEvent;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnCreateContextMenuListener;
-import android.webkit.WebView;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.GridView;
-
-/**
- * This class implements a Grid layout of Views for the Tab picker.
- */
-class ImageGrid extends GridView implements OnItemClickListener, 
-        OnCreateContextMenuListener  {
-    
-    private Listener     mListener;
-    private ImageAdapter mAdapter;
-    private boolean      mIsLive;
-    private static final int SPACING = 10;
-    public static final int CANCEL  = -99;
-    public static final int NEW_TAB = -1;
-
-    /**
-     * Constructor
-     * @param context Context to use when inflating resources.
-     * @param live  TRUE if the view can accept touch or click
-     * @param l     Listener to respond to clicks etc.
-     */
-    public ImageGrid(Context context, boolean live, Listener l) {
-        super(context);
-
-        mIsLive = live;
-        if (live) {
-            setFocusable(true);
-            setFocusableInTouchMode(true);
-            setOnItemClickListener(this);
-            setOnCreateContextMenuListener(this);
-        }
-        mListener = l;
-
-        mAdapter = new ImageAdapter(context, this, live);
-        setAdapter(mAdapter);
-
-        setBackgroundColor(0xFF000000);
-
-        setVerticalSpacing(SPACING);
-        setHorizontalSpacing(SPACING);
-        setNumColumns(2);
-        setStretchMode(GridView.STRETCH_COLUMN_WIDTH);
-        setSelector(android.R.drawable.gallery_thumb);
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        // We always consume the BACK key even if mListener is null or the
-        // ImageGrid is not "live." This prevents crashes during tab animations
-        // if the user presses BACK.
-        if ((event.getAction() == KeyEvent.ACTION_DOWN) &&
-                (event.getKeyCode() == KeyEvent.KEYCODE_BACK)) {
-            if (mListener != null && mIsLive) {
-                mListener.onClick(CANCEL);
-                invalidate();
-            }
-            return true;
-        }
-        return super.dispatchKeyEvent(event);
-    }
-    
-    /**
-     * Called by BrowserActivity to add a new window to the tab picker.
-     * This does not happen dynamically, this only happens during view
-     * setup.
-     * 
-     * @param v Webview of the tab to add
-     * @param name Web page title
-     * @param url URL of the webpage
-     */
-    public void add(TabControl.Tab t) {
-        mAdapter.add(t);
-    }
-
-    /**
-     * Called by BrowserActivity when a window has been removed from the
-     * tab list.
-     * 
-     * @param index Window to remove, from 0 to MAX_TABS-1
-     */
-    public void remove(int index) {
-        if (Browser.DEBUG && (index < 0 || index >= TabControl.MAX_TABS)) {
-            throw new AssertionError();
-        }
-        mAdapter.remove(index);
-    }
-
-    /**
-     * Request focus to initially set to a particular tab. 
-     *
-     * @param startingIndex This is a Tab index from 0 - MAX_TABS-1 and does not
-     *                      include the "New Tab" cell.
-     */
-    public void setCurrentIndex(int startingIndex) {
-        if (!mAdapter.maxedOut()) {
-            startingIndex++;
-        }
-        setSelection(startingIndex);
-    }
-
-    public Listener getListener() {
-        return mListener;
-    }
-
-    public void setListener(Listener l) {
-        mListener = l;
-    }
-
-    /**
-     * Return true if the ImageGrid is live. This means that tabs can be chosen
-     * and the menu can be invoked.
-     */
-    public boolean isLive() {
-        return mIsLive;
-    }
-
-    /**
-     * Do some internal cleanup of the ImageGrid's adapter.
-     */
-    public void clear() {
-        mAdapter.clear();
-    }
-
-    /* (non-Javadoc)
-     * @see android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget.AdapterView, android.view.View, int, long)
-     */
-    public void onItemClick(AdapterView parent, View v, int position, long id) {
-        if (!mAdapter.maxedOut()) {
-            position--;
-        }
-        // Position will be -1 for the "New Tab" cell.
-        if (mListener != null) {
-            mListener.onClick(position);
-        }
-    }
-    
-    /* (non-Javadoc)
-     * @see android.view.View.OnCreateContextMenuListener#onCreateContextMenu(android.view.ContextMenu, android.view.View, java.lang.Object)
-     */
-    public void onCreateContextMenu(ContextMenu menu, View v, 
-            ContextMenuInfo menuInfo) {
-        // Do not create the context menu if there is no listener or the Tab
-        // overview is not "live."
-        if (mListener == null || !mIsLive) {
-            return;
-        }
-        AdapterView.AdapterContextMenuInfo info = 
-                (AdapterView.AdapterContextMenuInfo) menuInfo;
-        boolean maxed = mAdapter.maxedOut();
-        if (info.position > 0 || maxed) {
-            MenuInflater inflater = new MenuInflater(mContext);
-            inflater.inflate(R.menu.tabscontext, menu);
-            int position = info.position;
-            if (!maxed) {
-                position--;
-            }
-            menu.setHeaderTitle(mAdapter.mItems.get(position).getTitle());
-        }
-    }
-
-    // convert a context menu position to an actual tab position. Since context
-    // menus are not created for the "New Tab" cell, this will always return a
-    // valid tab position.
-    public int getContextMenuPosition(MenuItem menu) {
-        AdapterView.AdapterContextMenuInfo info =
-                (AdapterView.AdapterContextMenuInfo) menu.getMenuInfo();
-        int pos = info.position;
-        if (!mAdapter.maxedOut()) {
-            pos--;
-        }
-        return pos;
-    }
-    
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        // Called when our orientation changes. Tell the adapter about the new
-        // size. Compute the individual tab height by taking the grid height
-        // and subtracting the SPACING. Then subtract the list padding twice
-        // (once for each tab on screen) and divide the remaining height by 2.
-        int tabHeight = (h - SPACING
-                - 2 * (getListPaddingTop() + getListPaddingBottom())) / 2;
-        mAdapter.heightChanged(tabHeight);
-        super.onSizeChanged(w, h, oldw, oldh);
-    }
-
-    /**
-     * Listener to be notified by behavior of ImageGrid.
-     */
-    public interface Listener {
-        /**
-         * Called when enter is pressed on the list.
-         * @param position  The index of the selected image when
-         *                  enter is pressed.
-         */
-        void onClick(int position);
-
-        /**
-         * Called when remove is called on the grid.
-         */
-        void remove(int position);
-    }
-
-}
diff --git a/src/com/android/browser/KeyTracker.java b/src/com/android/browser/KeyTracker.java
deleted file mode 100644 (file)
index 344e4f8..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2006 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.view.KeyEvent;
-import android.view.ViewConfiguration;
-
-class KeyTracker {
-
-    public enum Stage {
-        DOWN,           //!< the key has just been pressed
-        SHORT_REPEAT,   //!< repeated key, but duration is under the long-press threshold
-        LONG_REPEAT,    //!< repeated key, but duration is over the long-press threshold
-        UP              //!< the key is being released
-    }
-    
-    public enum State {
-        KEEP_TRACKING,  //!< return this to continue to track the key
-        DONE_TRACKING,  //!< return this if you handled the key, but need not track it anymore
-        NOT_TRACKING    //!< return this if you will not handle this key
-    }
-    
-    public interface OnKeyTracker {
-
-        /** Called whenever there is a key event [down, short/long repeat, up]
-            @param keyCode  The current keyCode (see KeyEvent class)
-            @param msg      The message associated with the keyCode
-            @maram stage    The state the key press is in [down, short/long repeat, up]
-            @param duration The number of milliseconds since this key was initially pressed
-            @return your state after seeing the key. If you return DONE_TRACKING or NOT_TRACKING,
-                    you will not be called again for the lifetime of this key event.
-        */
-        public State onKeyTracker(int keyCode, KeyEvent event, Stage stage, int duration);
-    }
-    
-    public KeyTracker(OnKeyTracker tracker) {
-        mTracker = tracker;
-    }
-    
-    public boolean doKeyDown(int keyCode, KeyEvent event) {
-        long now = System.currentTimeMillis();
-        Stage stage = null;
-
-        // check if its a new/different key
-        if (mKeyCode != keyCode || event.getRepeatCount() == 0) {
-            mKeyCode = keyCode;
-            mStartMS = now;
-            stage = Stage.DOWN;
-        }
-        else if (mState == State.KEEP_TRACKING) {
-            stage = (now - mStartMS) >= LONG_PRESS_DURATION_MS ? Stage.LONG_REPEAT : Stage.SHORT_REPEAT;
-        }
-
-        if (stage != null) {
-            mEvent = event;        
-            callTracker(stage, now);
-        }
-
-        return mState != State.NOT_TRACKING;
-    }
-    
-    public boolean doKeyUp(int keyCode, KeyEvent event) {
-        boolean handled = false;
-
-        if (mState == State.KEEP_TRACKING && mKeyCode == keyCode) {
-            mEvent = event;
-            callTracker(Stage.UP, System.currentTimeMillis());
-            handled = mState != State.NOT_TRACKING;
-        }
-        mKeyCode = NOT_A_KEYCODE;
-        return handled;
-    }
-    
-    private void callTracker(Stage stage, long now) {
-        mState = mTracker.onKeyTracker(mKeyCode, mEvent, stage, (int)(now - mStartMS));
-    }
-    
-    private void dump() {
-        System.out.println(" key=" + mKeyCode + " dur=" + (System.currentTimeMillis() - mStartMS) +
-                            " state=" + mState);
-    }
-
-    private int             mKeyCode = NOT_A_KEYCODE;
-    private KeyEvent        mEvent;
-    private long            mStartMS;
-    private State           mState;
-    private OnKeyTracker    mTracker;
-
-    private static final int LONG_PRESS_DURATION_MS = 
-            ViewConfiguration.getLongPressTimeout();
-    private static final int NOT_A_KEYCODE = -123456;
-}
-