move android-common from framework/base to framework/ex

Also change the LOCAL_SDK_VERSION from current to 8.
Change-Id: I68943b8b41622dab88c7b13d8c067b39205f028e
diff --git a/common/Android.mk b/common/Android.mk
new file mode 100644
index 0000000..c8dfffc
--- /dev/null
+++ b/common/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2009 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# Note: the source code is in java/, not src/, because this code is also part of
+# the framework library, and build/core/pathmap.mk expects a java/ subdirectory.
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-common
+LOCAL_SDK_VERSION := 8
+LOCAL_SRC_FILES := \
+     $(call all-java-files-under, java) \
+     $(call all-logtags-files-under, java)
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Include this library in the build server's output directory
+$(call dist-for-goals, droid, $(LOCAL_BUILT_MODULE):android-common.jar)
+
+# Build the test package
+# we can't build the test for apps only build, because android.test.runner is not unbundled yet.
+ifeq ($(TARGET_BUILD_APPS),)
+include $(call all-makefiles-under, $(LOCAL_PATH))
+endif
diff --git a/common/java/com/android/common/ArrayListCursor.java b/common/java/com/android/common/ArrayListCursor.java
new file mode 100644
index 0000000..9ad5c36
--- /dev/null
+++ b/common/java/com/android/common/ArrayListCursor.java
@@ -0,0 +1,167 @@
+/*
+ * 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.common;
+
+import android.database.AbstractCursor;
+import android.database.CursorWindow;
+
+import java.lang.System;
+import java.util.ArrayList;
+
+/**
+ * A convenience class that presents a two-dimensional ArrayList
+ * as a Cursor.
+ * @deprecated This is has been replaced by MatrixCursor.
+*/
+public class ArrayListCursor extends AbstractCursor {
+    private String[] mColumnNames;
+    private ArrayList<Object>[] mRows;
+
+    @SuppressWarnings({"unchecked"})
+    public ArrayListCursor(String[] columnNames, ArrayList<ArrayList> rows) {
+        int colCount = columnNames.length;
+        boolean foundID = false;
+        // Add an _id column if not in columnNames
+        for (int i = 0; i < colCount; ++i) {
+            if (columnNames[i].compareToIgnoreCase("_id") == 0) {
+                mColumnNames = columnNames;
+                foundID = true;
+                break;
+            }
+        }
+
+        if (!foundID) {
+            mColumnNames = new String[colCount + 1];
+            System.arraycopy(columnNames, 0, mColumnNames, 0, columnNames.length);
+            mColumnNames[colCount] = "_id";
+        }
+
+        int rowCount = rows.size();
+        mRows = new ArrayList[rowCount];
+
+        for (int i = 0; i < rowCount; ++i) {
+            mRows[i] = rows.get(i);
+            if (!foundID) {
+                mRows[i].add(i);
+            }
+        }
+    }
+
+    @Override
+    public void fillWindow(int position, CursorWindow window) {
+        if (position < 0 || position > getCount()) {
+            return;
+        }
+
+        window.acquireReference();
+        try {
+            int oldpos = mPos;
+            mPos = position - 1;
+            window.clear();
+            window.setStartPosition(position);
+            int columnNum = getColumnCount();
+            window.setNumColumns(columnNum);
+            while (moveToNext() && window.allocRow()) {
+                for (int i = 0; i < columnNum; i++) {
+                    final Object data = mRows[mPos].get(i);
+                    if (data != null) {
+                        if (data instanceof byte[]) {
+                            byte[] field = (byte[]) data;
+                            if (!window.putBlob(field, mPos, i)) {
+                                window.freeLastRow();
+                                break;
+                            }
+                        } else {
+                            String field = data.toString();
+                            if (!window.putString(field, mPos, i)) {
+                                window.freeLastRow();
+                                break;
+                            }
+                        }
+                    } else {
+                        if (!window.putNull(mPos, i)) {
+                            window.freeLastRow();
+                            break;
+                        }
+                    }
+                }
+            }
+
+            mPos = oldpos;
+        } catch (IllegalStateException e){
+            // simply ignore it
+        } finally {
+            window.releaseReference();
+        }
+    }
+
+    @Override
+    public int getCount() {
+        return mRows.length;
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        return mColumnNames;
+    }
+
+    @Override
+    public byte[] getBlob(int columnIndex) {
+        return (byte[]) mRows[mPos].get(columnIndex);
+    }
+
+    @Override
+    public String getString(int columnIndex) {
+        Object cell = mRows[mPos].get(columnIndex);
+        return (cell == null) ? null : cell.toString();
+    }
+
+    @Override
+    public short getShort(int columnIndex) {
+        Number num = (Number) mRows[mPos].get(columnIndex);
+        return num.shortValue();
+    }
+
+    @Override
+    public int getInt(int columnIndex) {
+        Number num = (Number) mRows[mPos].get(columnIndex);
+        return num.intValue();
+    }
+
+    @Override
+    public long getLong(int columnIndex) {
+        Number num = (Number) mRows[mPos].get(columnIndex);
+        return num.longValue();
+    }
+
+    @Override
+    public float getFloat(int columnIndex) {
+        Number num = (Number) mRows[mPos].get(columnIndex);
+        return num.floatValue();
+    }
+
+    @Override
+    public double getDouble(int columnIndex) {
+        Number num = (Number) mRows[mPos].get(columnIndex);
+        return num.doubleValue();
+    }
+
+    @Override
+    public boolean isNull(int columnIndex) {
+        return mRows[mPos].get(columnIndex) == null;
+    }
+}
diff --git a/common/java/com/android/common/GoogleLogTags.logtags b/common/java/com/android/common/GoogleLogTags.logtags
new file mode 100644
index 0000000..f848ddf
--- /dev/null
+++ b/common/java/com/android/common/GoogleLogTags.logtags
@@ -0,0 +1,100 @@
+# 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.
+
+option java_package com.android.common
+
+#####
+# This file contains definitions for event log (android.util.EventLog) tags
+# used by Google Mobile Services applications.  These definitions are part of
+# the platform even when the Google applications are not.
+#
+# See system/core/logcat/event.logtags for a description of the file format.
+#
+# These event log tags must be assigned specific numbers (no "?") in the range
+# 200000-300000.  If new tags are added, be aware that older platforms will be
+# missing the tag definitions, and may not be able to show them in their logs.
+
+#####
+# System Update (OTA)
+
+# System update status bits
+# [31- 9] Reserved for future use
+# [ 8- 7] package verified (0=not attempted, 1=succeeded, 2=failed)
+# [    6] install approved
+# [    5] download approved
+# [ 4- 0] status
+201001 system_update (status|1|5),(download_result|1|5),(bytes|2|2),(url|3)
+201002 system_update_user (action|3)
+
+#####
+# Android Market
+
+# @param changes Number of changes made to database in reconstruct
+202001 vending_reconstruct (changes|1)
+
+#####
+# Google Services Framework
+
+203001 sync_details (authority|3),(send|1|2),(recv|1|2),(details|3)
+
+203002 google_http_request (elapsed|2|3),(status|1),(appname|3),(reused|1)
+
+#####
+# Google Talk Service
+
+# This event is logged when GTalkService encounters important events
+204001 gtalkservice (eventType|1)
+# This event is logged for GTalk connection state changes. The status field is an int, but
+# it really contains 4 separate values, each taking up a byte
+#     (eventType << 24) + (connection state << 16) + (connection error << 8) + network state
+204002 gtalk_connection (status|1)
+
+# This event is logged when GTalk connection is closed.
+# The status field is an int, but contains 2 different values, it's represented as
+#
+#     (networkType << 8) + connection error
+#
+# the possible error values are
+#
+# no_error=0, no_network=1, connection_failed=2, unknown_host=3, auth_failed=4,
+# auth_expired=5, heart_beat_timeout=6, server_error=7, server_reject_rate_limiting=8, unknown=10
+#
+# duration is the connection duration.
+204003 gtalk_conn_close (status|1),(duration|1)
+
+# This event is logged for GTalk heartbeat resets
+# interval_and_nt contains both the heartbeat interval and the network type, It's represented as
+#     (networkType << 16) + interval
+# interval is in seconds; network type can be 0 (mobile) or 1 (wifi); ip is the host ip addr.
+204004 gtalk_heartbeat_reset (interval_and_nt|1),(ip|3)
+
+# This event is logged when an Rmq v2 packet is sent or received.
+204005 c2dm (packet_type|1),(persistent_id|3),(stream_id|1),(last_stream_id|1)
+
+#####
+# Google Login Service and Setup Wizard
+
+# This event is for when communicating to the server times out during account setup
+205001 setup_server_timeout
+205002 setup_required_captcha (action|3)
+205003 setup_io_error (status|3)
+205004 setup_server_error
+205005 setup_retries_exhausted
+205006 setup_no_data_network
+205007 setup_completed
+
+205008 gls_account_tried (status|1)
+205009 gls_account_saved (status|1)
+205010 gls_authenticate (status|1),(service|3)
+205011 google_mail_switch (direction|1)
diff --git a/common/java/com/android/common/NetworkConnectivityListener.java b/common/java/com/android/common/NetworkConnectivityListener.java
new file mode 100644
index 0000000..b49b80d
--- /dev/null
+++ b/common/java/com/android/common/NetworkConnectivityListener.java
@@ -0,0 +1,224 @@
+/*
+ * 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.common;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * A wrapper for a broadcast receiver that provides network connectivity
+ * state information, independent of network type (mobile, Wi-Fi, etc.).
+ * @deprecated Code tempted to use this class should simply listen for connectivity intents
+ * (or poll ConnectivityManager) directly.
+ * {@hide}
+ */
+public class NetworkConnectivityListener {
+    private static final String TAG = "NetworkConnectivityListener";
+    private static final boolean DBG = false;
+
+    private Context mContext;
+    private HashMap<Handler, Integer> mHandlers = new HashMap<Handler, Integer>();
+    private State mState;
+    private boolean mListening;
+    private String mReason;
+    private boolean mIsFailover;
+
+    /** Network connectivity information */
+    private NetworkInfo mNetworkInfo;
+
+    /**
+     * In case of a Disconnect, the connectivity manager may have
+     * already established, or may be attempting to establish, connectivity
+     * with another network. If so, {@code mOtherNetworkInfo} will be non-null.
+     */
+    private NetworkInfo mOtherNetworkInfo;
+
+    private ConnectivityBroadcastReceiver mReceiver;
+
+    private class ConnectivityBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
+                mListening == false) {
+                Log.w(TAG, "onReceived() called with " + mState.toString() + " and " + intent);
+                return;
+            }
+
+            boolean noConnectivity =
+                intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+
+            if (noConnectivity) {
+                mState = State.NOT_CONNECTED;
+            } else {
+                mState = State.CONNECTED;
+            }
+
+            mNetworkInfo = (NetworkInfo)
+                intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
+            mOtherNetworkInfo = (NetworkInfo)
+                intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
+
+            mReason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON);
+            mIsFailover =
+                intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
+
+            if (DBG) {
+                Log.d(TAG, "onReceive(): mNetworkInfo=" + mNetworkInfo +  " mOtherNetworkInfo = "
+                        + (mOtherNetworkInfo == null ? "[none]" : mOtherNetworkInfo +
+                        " noConn=" + noConnectivity) + " mState=" + mState.toString());
+            }
+
+            // Notifiy any handlers.
+            Iterator<Handler> it = mHandlers.keySet().iterator();
+            while (it.hasNext()) {
+                Handler target = it.next();
+                Message message = Message.obtain(target, mHandlers.get(target));
+                target.sendMessage(message);
+            }
+        }
+    };
+
+    public enum State {
+        UNKNOWN,
+
+        /** This state is returned if there is connectivity to any network **/
+        CONNECTED,
+        /**
+         * This state is returned if there is no connectivity to any network. This is set
+         * to true under two circumstances:
+         * <ul>
+         * <li>When connectivity is lost to one network, and there is no other available
+         * network to attempt to switch to.</li>
+         * <li>When connectivity is lost to one network, and the attempt to switch to
+         * another network fails.</li>
+         */
+        NOT_CONNECTED
+    }
+
+    /**
+     * Create a new NetworkConnectivityListener.
+     */
+    public NetworkConnectivityListener() {
+        mState = State.UNKNOWN;
+        mReceiver = new ConnectivityBroadcastReceiver();
+    }
+
+    /**
+     * This method starts listening for network connectivity state changes.
+     * @param context
+     */
+    public synchronized void startListening(Context context) {
+        if (!mListening) {
+            mContext = context;
+
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+            context.registerReceiver(mReceiver, filter);
+            mListening = true;
+        }
+    }
+
+    /**
+     * This method stops this class from listening for network changes.
+     */
+    public synchronized void stopListening() {
+        if (mListening) {
+            mContext.unregisterReceiver(mReceiver);
+            mContext = null;
+            mNetworkInfo = null;
+            mOtherNetworkInfo = null;
+            mIsFailover = false;
+            mReason = null;
+            mListening = false;
+        }
+    }
+
+    /**
+     * This methods registers a Handler to be called back onto with the specified what code when
+     * the network connectivity state changes.
+     *
+     * @param target The target handler.
+     * @param what The what code to be used when posting a message to the handler.
+     */
+    public void registerHandler(Handler target, int what) {
+        mHandlers.put(target, what);
+    }
+
+    /**
+     * This methods unregisters the specified Handler.
+     * @param target
+     */
+    public void unregisterHandler(Handler target) {
+        mHandlers.remove(target);
+    }
+
+    public State getState() {
+        return mState;
+    }
+
+    /**
+     * Return the NetworkInfo associated with the most recent connectivity event.
+     * @return {@code NetworkInfo} for the network that had the most recent connectivity event.
+     */
+    public NetworkInfo getNetworkInfo() {
+        return mNetworkInfo;
+    }
+
+    /**
+     * If the most recent connectivity event was a DISCONNECT, return
+     * any information supplied in the broadcast about an alternate
+     * network that might be available. If this returns a non-null
+     * value, then another broadcast should follow shortly indicating
+     * whether connection to the other network succeeded.
+     *
+     * @return NetworkInfo
+     */
+    public NetworkInfo getOtherNetworkInfo() {
+        return mOtherNetworkInfo;
+    }
+
+    /**
+     * Returns true if the most recent event was for an attempt to switch over to
+     * a new network following loss of connectivity on another network.
+     * @return {@code true} if this was a failover attempt, {@code false} otherwise.
+     */
+    public boolean isFailover() {
+        return mIsFailover;
+    }
+
+    /**
+     * An optional reason for the connectivity state change may have been supplied.
+     * This returns it.
+     * @return the reason for the state change, if available, or {@code null}
+     * otherwise.
+     */
+    public String getReason() {
+        return mReason;
+    }
+}
diff --git a/common/java/com/android/common/OperationScheduler.java b/common/java/com/android/common/OperationScheduler.java
new file mode 100644
index 0000000..1786957
--- /dev/null
+++ b/common/java/com/android/common/OperationScheduler.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2009 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.common;
+
+import android.content.SharedPreferences;
+import android.net.http.AndroidHttpClient;
+import android.text.format.Time;
+
+import java.util.Map;
+import java.util.TreeSet;
+
+/**
+ * Tracks the success/failure history of a particular network operation in
+ * persistent storage and computes retry strategy accordingly.  Handles
+ * exponential backoff, periodic rescheduling, event-driven triggering,
+ * retry-after moratorium intervals, etc. based on caller-specified parameters.
+ *
+ * <p>This class does not directly perform or invoke any operations,
+ * it only keeps track of the schedule.  Somebody else needs to call
+ * {@link #getNextTimeMillis()} as appropriate and do the actual work.
+ */
+public class OperationScheduler {
+    /** Tunable parameter options for {@link #getNextTimeMillis}. */
+    public static class Options {
+        /** Wait this long after every error before retrying. */
+        public long backoffFixedMillis = 0;
+
+        /** Wait this long times the number of consecutive errors so far before retrying. */
+        public long backoffIncrementalMillis = 5000;
+
+        /** Maximum duration of moratorium to honor.  Mostly an issue for clock rollbacks. */
+        public long maxMoratoriumMillis = 24 * 3600 * 1000;
+
+        /** Minimum duration after success to wait before allowing another trigger. */
+        public long minTriggerMillis = 0;
+
+        /** Automatically trigger this long after the last success. */
+        public long periodicIntervalMillis = 0;
+
+        @Override
+        public String toString() {
+            return String.format(
+                    "OperationScheduler.Options[backoff=%.1f+%.1f max=%.1f min=%.1f period=%.1f]",
+                    backoffFixedMillis / 1000.0, backoffIncrementalMillis / 1000.0,
+                    maxMoratoriumMillis / 1000.0, minTriggerMillis / 1000.0,
+                    periodicIntervalMillis / 1000.0);
+        }
+    }
+
+    private static final String PREFIX = "OperationScheduler_";
+    private final SharedPreferences mStorage;
+
+    /**
+     * Initialize the scheduler state.
+     * @param storage to use for recording the state of operations across restarts/reboots
+     */
+    public OperationScheduler(SharedPreferences storage) {
+        mStorage = storage;
+    }
+
+    /**
+     * Parse scheduler options supplied in this string form:
+     *
+     * <pre>
+     * backoff=(fixed)+(incremental) max=(maxmoratorium) min=(mintrigger) [period=](interval)
+     * </pre>
+     *
+     * All values are times in (possibly fractional) <em>seconds</em> (not milliseconds).
+     * Omitted settings are left at whatever existing default value was passed in.
+     *
+     * <p>
+     * The default options: <code>backoff=0+5 max=86400 min=0 period=0</code><br>
+     * Fractions are OK: <code>backoff=+2.5 period=10.0</code><br>
+     * The "period=" can be omitted: <code>3600</code><br>
+     *
+     * @param spec describing some or all scheduler options.
+     * @param options to update with parsed values.
+     * @return the options passed in (for convenience)
+     * @throws IllegalArgumentException if the syntax is invalid
+     */
+    public static Options parseOptions(String spec, Options options)
+            throws IllegalArgumentException {
+        for (String param : spec.split(" +")) {
+            if (param.length() == 0) continue;
+            if (param.startsWith("backoff=")) {
+                int plus = param.indexOf('+', 8);
+                if (plus < 0) {
+                    options.backoffFixedMillis = parseSeconds(param.substring(8));
+                } else {
+                    if (plus > 8) {
+                        options.backoffFixedMillis = parseSeconds(param.substring(8, plus));
+                    }
+                    options.backoffIncrementalMillis = parseSeconds(param.substring(plus + 1));
+                }
+            } else if (param.startsWith("max=")) {
+                options.maxMoratoriumMillis = parseSeconds(param.substring(4));
+            } else if (param.startsWith("min=")) {
+                options.minTriggerMillis = parseSeconds(param.substring(4));
+            } else if (param.startsWith("period=")) {
+                options.periodicIntervalMillis = parseSeconds(param.substring(7));
+            } else {
+                options.periodicIntervalMillis = parseSeconds(param);
+            }
+        }
+        return options;
+    }
+
+    private static long parseSeconds(String param) throws NumberFormatException {
+        return (long) (Float.parseFloat(param) * 1000);
+    }
+
+    /**
+     * Compute the time of the next operation.  Does not modify any state
+     * (unless the clock rolls backwards, in which case timers are reset).
+     *
+     * @param options to use for this computation.
+     * @return the wall clock time ({@link System#currentTimeMillis()}) when the
+     * next operation should be attempted -- immediately, if the return value is
+     * before the current time.
+     */
+    public long getNextTimeMillis(Options options) {
+        boolean enabledState = mStorage.getBoolean(PREFIX + "enabledState", true);
+        if (!enabledState) return Long.MAX_VALUE;
+
+        boolean permanentError = mStorage.getBoolean(PREFIX + "permanentError", false);
+        if (permanentError) return Long.MAX_VALUE;
+
+        // We do quite a bit of limiting to prevent a clock rollback from totally
+        // hosing the scheduler.  Times which are supposed to be in the past are
+        // clipped to the current time so we don't languish forever.
+
+        int errorCount = mStorage.getInt(PREFIX + "errorCount", 0);
+        long now = currentTimeMillis();
+        long lastSuccessTimeMillis = getTimeBefore(PREFIX + "lastSuccessTimeMillis", now);
+        long lastErrorTimeMillis = getTimeBefore(PREFIX + "lastErrorTimeMillis", now);
+        long triggerTimeMillis = mStorage.getLong(PREFIX + "triggerTimeMillis", Long.MAX_VALUE);
+        long moratoriumSetMillis = getTimeBefore(PREFIX + "moratoriumSetTimeMillis", now);
+        long moratoriumTimeMillis = getTimeBefore(PREFIX + "moratoriumTimeMillis",
+                moratoriumSetMillis + options.maxMoratoriumMillis);
+
+        long time = triggerTimeMillis;
+        if (options.periodicIntervalMillis > 0) {
+            time = Math.min(time, lastSuccessTimeMillis + options.periodicIntervalMillis);
+        }
+
+        time = Math.max(time, moratoriumTimeMillis);
+        time = Math.max(time, lastSuccessTimeMillis + options.minTriggerMillis);
+        if (errorCount > 0) {
+            time = Math.max(time, lastErrorTimeMillis + options.backoffFixedMillis +
+                    options.backoffIncrementalMillis * errorCount);
+        }
+        return time;
+    }
+
+    /**
+     * Return the last time the operation completed.  Does not modify any state.
+     *
+     * @return the wall clock time when {@link #onSuccess()} was last called.
+     */
+    public long getLastSuccessTimeMillis() {
+        return mStorage.getLong(PREFIX + "lastSuccessTimeMillis", 0);
+    }
+
+    /**
+     * Return the last time the operation was attempted.  Does not modify any state.
+     *
+     * @return the wall clock time when {@link #onSuccess()} or {@link
+     * #onTransientError()} was last called.
+     */
+    public long getLastAttemptTimeMillis() {
+        return Math.max(
+                mStorage.getLong(PREFIX + "lastSuccessTimeMillis", 0),
+                mStorage.getLong(PREFIX + "lastErrorTimeMillis", 0));
+    }
+
+    /**
+     * Fetch a {@link SharedPreferences} property, but force it to be before
+     * a certain time, updating the value if necessary.  This is to recover
+     * gracefully from clock rollbacks which could otherwise strand our timers.
+     *
+     * @param name of SharedPreferences key
+     * @param max time to allow in result
+     * @return current value attached to key (default 0), limited by max
+     */
+    private long getTimeBefore(String name, long max) {
+        long time = mStorage.getLong(name, 0);
+        if (time > max) mStorage.edit().putLong(name, (time = max)).commit();
+        return time;
+    }
+
+    /**
+     * Request an operation to be performed at a certain time.  The actual
+     * scheduled time may be affected by error backoff logic and defined
+     * minimum intervals.  Use {@link Long#MAX_VALUE} to disable triggering.
+     *
+     * @param millis wall clock time ({@link System#currentTimeMillis()}) to
+     * trigger another operation; 0 to trigger immediately
+     */
+    public void setTriggerTimeMillis(long millis) {
+        mStorage.edit().putLong(PREFIX + "triggerTimeMillis", millis).commit();
+    }
+
+    /**
+     * Forbid any operations until after a certain (absolute) time.
+     * Limited by {@link #Options.maxMoratoriumMillis}.
+     *
+     * @param millis wall clock time ({@link System#currentTimeMillis()})
+     * when operations should be allowed again; 0 to remove moratorium
+     */
+    public void setMoratoriumTimeMillis(long millis) {
+        mStorage.edit()
+                .putLong(PREFIX + "moratoriumTimeMillis", millis)
+                .putLong(PREFIX + "moratoriumSetTimeMillis", currentTimeMillis())
+                .commit();
+    }
+
+    /**
+     * Forbid any operations until after a certain time, as specified in
+     * the format used by the HTTP "Retry-After" header.
+     * Limited by {@link #Options.maxMoratoriumMillis}.
+     *
+     * @param retryAfter moratorium time in HTTP format
+     * @return true if a time was successfully parsed
+     */
+    public boolean setMoratoriumTimeHttp(String retryAfter) {
+        try {
+            long ms = Long.valueOf(retryAfter) * 1000;
+            setMoratoriumTimeMillis(ms + currentTimeMillis());
+            return true;
+        } catch (NumberFormatException nfe) {
+            try {
+                setMoratoriumTimeMillis(AndroidHttpClient.parseDate(retryAfter));
+                return true;
+            } catch (IllegalArgumentException iae) {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Enable or disable all operations.  When disabled, all calls to
+     * {@link #getNextTimeMillis()} return {@link Long#MAX_VALUE}.
+     * Commonly used when data network availability goes up and down.
+     *
+     * @param enabled if operations can be performed
+     */
+    public void setEnabledState(boolean enabled) {
+        mStorage.edit().putBoolean(PREFIX + "enabledState", enabled).commit();
+    }
+
+    /**
+     * Report successful completion of an operation.  Resets all error
+     * counters, clears any trigger directives, and records the success.
+     */
+    public void onSuccess() {
+        resetTransientError();
+        resetPermanentError();
+        mStorage.edit()
+                .remove(PREFIX + "errorCount")
+                .remove(PREFIX + "lastErrorTimeMillis")
+                .remove(PREFIX + "permanentError")
+                .remove(PREFIX + "triggerTimeMillis")
+                .putLong(PREFIX + "lastSuccessTimeMillis", currentTimeMillis()).commit();
+    }
+
+    /**
+     * Report a transient error (usually a network failure).  Increments
+     * the error count and records the time of the latest error for backoff
+     * purposes.
+     */
+    public void onTransientError() {
+        mStorage.edit().putLong(PREFIX + "lastErrorTimeMillis", currentTimeMillis()).commit();
+        mStorage.edit().putInt(PREFIX + "errorCount",
+                mStorage.getInt(PREFIX + "errorCount", 0) + 1).commit();
+    }
+
+    /**
+     * Reset all transient error counts, allowing the next operation to proceed
+     * immediately without backoff.  Commonly used on network state changes, when
+     * partial progress occurs (some data received), and in other circumstances
+     * where there is reason to hope things might start working better.
+     */
+    public void resetTransientError() {
+        mStorage.edit().remove(PREFIX + "errorCount").commit();
+    }
+
+    /**
+     * Report a permanent error that will not go away until further notice.
+     * No operation will be scheduled until {@link #resetPermanentError()}
+     * is called.  Commonly used for authentication failures (which are reset
+     * when the accounts database is updated).
+     */
+    public void onPermanentError() {
+        mStorage.edit().putBoolean(PREFIX + "permanentError", true).commit();
+    }
+
+    /**
+     * Reset any permanent error status set by {@link #onPermanentError},
+     * allowing operations to be scheduled as normal.
+     */
+    public void resetPermanentError() {
+        mStorage.edit().remove(PREFIX + "permanentError").commit();
+    }
+
+    /**
+     * Return a string description of the scheduler state for debugging.
+     */
+    public String toString() {
+        StringBuilder out = new StringBuilder("[OperationScheduler:");
+        for (String key : new TreeSet<String>(mStorage.getAll().keySet())) {  // Sort keys
+            if (key.startsWith(PREFIX)) {
+                if (key.endsWith("TimeMillis")) {
+                    Time time = new Time();
+                    time.set(mStorage.getLong(key, 0));
+                    out.append(" ").append(key.substring(PREFIX.length(), key.length() - 10));
+                    out.append("=").append(time.format("%Y-%m-%d/%H:%M:%S"));
+                } else {
+                    out.append(" ").append(key.substring(PREFIX.length()));
+                    out.append("=").append(mStorage.getAll().get(key).toString());
+                }
+            }
+        }
+        return out.append("]").toString();
+    }
+
+    /**
+     * Gets the current time.  Can be overridden for unit testing.
+     *
+     * @return {@link System#currentTimeMillis()}
+     */
+    protected long currentTimeMillis() {
+        return System.currentTimeMillis();
+    }
+}
diff --git a/common/java/com/android/common/Rfc822InputFilter.java b/common/java/com/android/common/Rfc822InputFilter.java
new file mode 100644
index 0000000..6dfdc7b
--- /dev/null
+++ b/common/java/com/android/common/Rfc822InputFilter.java
@@ -0,0 +1,74 @@
+/*
+ * 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.common;
+
+import android.text.InputFilter;
+import android.text.Spanned;
+import android.text.SpannableStringBuilder;
+
+/**
+ * Implements special address cleanup rules:
+ * The first space key entry following an "@" symbol that is followed by any combination
+ * of letters and symbols, including one+ dots and zero commas, should insert an extra
+ * comma (followed by the space).
+ *
+ * @hide
+ */
+public class Rfc822InputFilter implements InputFilter {
+
+    public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
+        int dstart, int dend) {
+
+        // quick check - did they enter a single space?
+        if (end-start != 1 || source.charAt(start) != ' ') {
+            return null;
+        }
+
+        // determine if the characters before the new space fit the pattern
+        // follow backwards and see if we find a comma, dot, or @
+        int scanBack = dstart;
+        boolean dotFound = false;
+        while (scanBack > 0) {
+            char c = dest.charAt(--scanBack);
+            switch (c) {
+                case '.':
+                    dotFound = true;    // one or more dots are req'd
+                    break;
+                case ',':
+                    return null;
+                case '@':
+                    if (!dotFound) {
+                        return null;
+                    }
+                    // we have found a comma-insert case.  now just do it
+                    // in the least expensive way we can.
+                    if (source instanceof Spanned) {
+                        SpannableStringBuilder sb = new SpannableStringBuilder(",");
+                        sb.append(source);
+                        return sb;
+                    } else {
+                        return ", ";
+                    }
+                default:
+                    // just keep going
+            }
+        }
+
+        // no termination cases were found, so don't edit the input
+        return null;
+    }
+}
diff --git a/common/java/com/android/common/Rfc822Validator.java b/common/java/com/android/common/Rfc822Validator.java
new file mode 100644
index 0000000..087e425
--- /dev/null
+++ b/common/java/com/android/common/Rfc822Validator.java
@@ -0,0 +1,134 @@
+/*
+ * 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.common;
+
+import android.text.TextUtils;
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
+import android.widget.AutoCompleteTextView;
+
+import java.util.regex.Pattern;
+
+/**
+ * This class works as a Validator for AutoCompleteTextView for
+ * email addresses.  If a token does not appear to be a valid address,
+ * it is trimmed of characters that cannot legitimately appear in one
+ * and has the specified domain name added.  It is meant for use with
+ * {@link Rfc822Token} and {@link Rfc822Tokenizer}.
+ *
+ * @deprecated In the future make sure we don't quietly alter the user's
+ *             text in ways they did not intend.  Meanwhile, hide this
+ *             class from the public API because it does not even have
+ *             a full understanding of the syntax it claims to correct.
+ * @hide
+ */
+public class Rfc822Validator implements AutoCompleteTextView.Validator {
+    /*
+     * Regex.EMAIL_ADDRESS_PATTERN hardcodes the TLD that we accept, but we
+     * want to make sure we will keep accepting email addresses with TLD's
+     * that don't exist at the time of this writing, so this regexp relaxes
+     * that constraint by accepting any kind of top level domain, not just
+     * ".com", ".fr", etc...
+     */
+    private static final Pattern EMAIL_ADDRESS_PATTERN =
+            Pattern.compile("[^\\s@]+@[^\\s@]+\\.[a-zA-z][a-zA-Z][a-zA-Z]*");
+
+    private String mDomain;
+
+    /**
+     * Constructs a new validator that uses the specified domain name as
+     * the default when none is specified.
+     */
+    public Rfc822Validator(String domain) {
+        mDomain = domain;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isValid(CharSequence text) {
+        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(text);
+
+        return tokens.length == 1 &&
+               EMAIL_ADDRESS_PATTERN.
+                   matcher(tokens[0].getAddress()).matches();
+    }
+
+    /**
+     * @return a string in which all the characters that are illegal for the username
+     * or the domain name part of the email address have been removed.
+     */
+    private String removeIllegalCharacters(String s) {
+        StringBuilder result = new StringBuilder();
+        int length = s.length();
+        for (int i = 0; i < length; i++) {
+            char c = s.charAt(i);
+
+            /*
+             * An RFC822 atom can contain any ASCII printing character
+             * except for periods and any of the following punctuation.
+             * A local-part can contain multiple atoms, concatenated by
+             * periods, so do allow periods here.
+             */
+
+            if (c <= ' ' || c > '~') {
+                continue;
+            }
+
+            if (c == '(' || c == ')' || c == '<' || c == '>' ||
+                c == '@' || c == ',' || c == ';' || c == ':' ||
+                c == '\\' || c == '"' || c == '[' || c == ']') {
+                continue;
+            }
+
+            result.append(c);
+        }
+        return result.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public CharSequence fixText(CharSequence cs) {
+        // Return an empty string if the email address only contains spaces, \n or \t
+        if (TextUtils.getTrimmedLength(cs) == 0) return "";
+
+        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(cs);
+        StringBuilder sb = new StringBuilder();
+
+        for (int i = 0; i < tokens.length; i++) {
+            String text = tokens[i].getAddress();
+            int index = text.indexOf('@');
+            if (index < 0) {
+                // If there is no @, just append the domain of the account
+                tokens[i].setAddress(removeIllegalCharacters(text) + "@" + mDomain);
+            } else {
+                // Otherwise, remove the illegal characters on both sides of the '@'
+                String fix = removeIllegalCharacters(text.substring(0, index));
+                String domain = removeIllegalCharacters(text.substring(index + 1));
+                tokens[i].setAddress(fix + "@" + (domain.length() != 0 ? domain : mDomain));
+            }
+
+            sb.append(tokens[i].toString());
+            if (i + 1 < tokens.length) {
+                sb.append(", ");
+            }
+        }
+
+        return sb;
+    }
+}
diff --git a/common/java/com/android/common/Search.java b/common/java/com/android/common/Search.java
new file mode 100644
index 0000000..55fa6f5
--- /dev/null
+++ b/common/java/com/android/common/Search.java
@@ -0,0 +1,38 @@
+/*
+ * 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.common;
+
+/**
+ * Utilities for search implementations.
+ *
+ * @see android.app.SearchManager
+ */
+public class Search {
+
+    /**
+     * Key for the source identifier set by the application that launched a search intent.
+     * The identifier is search-source specific string. It can be used
+     * by the search provider to keep statistics of where searches are started from.
+     *
+     * The source identifier is stored in the {@link android.app.SearchManager#APP_DATA}
+     * Bundle in {@link android.content.Intent#ACTION_SEARCH} and
+     * {@link android.content.Intent#ACTION_WEB_SEARCH} intents.
+     */
+    public final static String SOURCE = "source";
+
+    private Search() { }   // don't instantiate
+}
diff --git a/common/java/com/android/common/speech/LoggingEvents.java b/common/java/com/android/common/speech/LoggingEvents.java
new file mode 100644
index 0000000..1f3c6ef
--- /dev/null
+++ b/common/java/com/android/common/speech/LoggingEvents.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.common.speech;
+
+/**
+ * Logging event constants used for Voice Search and VoiceIME. These are the
+ * keys and values of extras to be specified in logging broadcast intents.
+ * This class is used by clients of the android.speech APIs to log how the
+ * user interacts with the IME settings and speech recognition result.
+ */
+public class LoggingEvents {
+    // The name of the broadcast intent for logging.
+    public static final String ACTION_LOG_EVENT = "com.android.common.speech.LOG_EVENT";
+
+    // The extra key used for the name of the app being logged.
+    public static final String EXTRA_APP_NAME = "app_name";
+
+    // The extra key used for the name of the app issuing the VoiceSearch
+    // or VoiceIME request
+    public static final String EXTRA_CALLING_APP_NAME = "";
+
+    // The extra key used for the event value. The possible event values depend
+    // on the app being logged for, and are defined in the subclasses below.
+    public static final String EXTRA_EVENT = "extra_event";
+
+    // The extra key used to log the time in milliseconds at which the EXTRA_EVENT
+    // occurred in the client.
+    public static final String EXTRA_TIMESTAMP = "timestamp";
+
+    // The extra key used (with a boolean value of 'true') as a way to trigger a
+    // flush of the log events to the server.
+    public static final String EXTRA_FLUSH = "flush";
+
+    /**
+     * Logging event constants for voice search. Below are the extra values for
+     * {@link LoggingEvents#EXTRA_EVENT}, clustered with keys to additional
+     * extras for some events that need to be included as additional fields in
+     * the event. Note that this is not representative of *all* voice search
+     * events - only the ones that need to be reported from outside the voice
+     * search app, such as from Browser.
+     */
+    public class VoiceSearch {
+        // The app name to be used for logging VoiceSearch events.
+        public static final String APP_NAME = "googlemobile";
+
+        public static final int RETRY = 0;
+
+        public static final int N_BEST_REVEAL = 1;
+
+        public static final int N_BEST_CHOOSE = 2;
+        public static final String EXTRA_N_BEST_CHOOSE_INDEX = "index";  // value should be int
+
+        public static final int QUERY_UPDATED = 3;
+        public static final String EXTRA_QUERY_UPDATED_VALUE = "value";  // value should be String
+    }
+
+    /**
+     * Logging event constants for VoiceIME. Below are the extra values for
+     * {@link LoggingEvents#EXTRA_EVENT}, clustered with keys to additional
+     * extras for some events that need to be included as additional fields in
+     * the event.
+     */
+    public class VoiceIme {
+        // The app name to be used for logging VoiceIME events.
+        public static final String APP_NAME = "voiceime";
+
+        public static final int KEYBOARD_WARNING_DIALOG_SHOWN = 0;
+
+        public static final int KEYBOARD_WARNING_DIALOG_DISMISSED = 1;
+
+        public static final int KEYBOARD_WARNING_DIALOG_OK = 2;
+
+        public static final int KEYBOARD_WARNING_DIALOG_CANCEL = 3;
+
+        public static final int SETTINGS_WARNING_DIALOG_SHOWN = 4;
+
+        public static final int SETTINGS_WARNING_DIALOG_DISMISSED = 5;
+
+        public static final int SETTINGS_WARNING_DIALOG_OK = 6;
+
+        public static final int SETTINGS_WARNING_DIALOG_CANCEL = 7;
+
+        public static final int SWIPE_HINT_DISPLAYED = 8;
+
+        public static final int PUNCTUATION_HINT_DISPLAYED = 9;
+
+        public static final int CANCEL_DURING_LISTENING = 10;
+
+        public static final int CANCEL_DURING_WORKING = 11;
+
+        public static final int CANCEL_DURING_ERROR = 12;
+
+        public static final int ERROR = 13;
+        public static final String EXTRA_ERROR_CODE = "code";  // value should be int
+
+        public static final int START = 14;
+        public static final String EXTRA_START_LOCALE = "locale";  // value should be String
+        public static final String EXTRA_START_SWIPE = "swipe";  // value should be boolean
+
+        public static final int VOICE_INPUT_DELIVERED = 15;
+
+        public static final int N_BEST_CHOOSE = 16;
+        public static final String EXTRA_N_BEST_CHOOSE_INDEX = "index";  // value should be int
+
+        public static final int TEXT_MODIFIED = 17;
+        public static final String EXTRA_TEXT_MODIFIED_LENGTH = "length";  // value should be int
+        public static final String EXTRA_TEXT_MODIFIED_TYPE = "type";  // value should be int below
+        public static final int TEXT_MODIFIED_TYPE_CHOOSE_SUGGESTION = 1;
+        public static final int TEXT_MODIFIED_TYPE_TYPING_DELETION = 2;
+        public static final int TEXT_MODIFIED_TYPE_TYPING_INSERTION = 3;
+        public static final int TEXT_MODIFIED_TYPE_TYPING_INSERTION_PUNCTUATION = 4;
+
+        public static final int INPUT_ENDED = 18;
+
+        public static final int VOICE_INPUT_SETTING_ENABLED = 19;
+
+        public static final int VOICE_INPUT_SETTING_DISABLED = 20;
+
+        public static final int IME_TEXT_ACCEPTED = 21;
+    }
+
+}
diff --git a/common/java/com/android/common/speech/Recognition.java b/common/java/com/android/common/speech/Recognition.java
new file mode 100644
index 0000000..1970179
--- /dev/null
+++ b/common/java/com/android/common/speech/Recognition.java
@@ -0,0 +1,61 @@
+/*
+ * 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.common.speech;
+
+/**
+ * Utilities for voice recognition implementations.
+ *
+ * @see android.speech.RecognitionService
+ * @see android.speech.RecognizerIntent
+ */
+public class Recognition {
+    
+    /**
+     * The key to the extra in the Bundle returned by
+     * android.speech.RecognizerIntent#ACTION_GET_LANGUAGE_DETAILS
+     * which is an ArrayList of CharSequences which are hints that can be shown to
+     * the user for voice actions currently supported by voice search for the user's current
+     * language preference for voice search (i.e., the one defined in the extra
+     * android.speech.RecognizerIntent#EXTRA_LANGUAGE_PREFERENCE).
+     *
+     * If this is paired with EXTRA_HINT_CONTEXT, should return a set of hints that are
+     * appropriate for the provided context.
+     *
+     * The CharSequences are SpannedStrings and will contain segments wrapped in
+     * <annotation action="true"></annotation>. This is to indicate the section of the text
+     * which represents the voice action, to be highlighted in the UI if so desired.
+     */
+    public static final String EXTRA_HINT_STRINGS = "android.speech.extra.HINT_STRINGS";
+    
+    /**
+     * The key to an extra to be included in the request intent for
+     * android.speech.RecognizerIntent#ACTION_GET_LANGUAGE_DETAILS.
+     * Should be an int of one of the values defined below. If an
+     * unknown int value is provided, it should be ignored.
+     */
+    public static final String EXTRA_HINT_CONTEXT = "android.speech.extra.HINT_CONTEXT";
+    
+    /**
+     * A set of values for EXTRA_HINT_CONTEXT.
+     */
+    public static final int HINT_CONTEXT_UNKNOWN = 0;
+    public static final int HINT_CONTEXT_VOICE_SEARCH_HELP = 1;
+    public static final int HINT_CONTEXT_CAR_HOME = 2;
+    public static final int HINT_CONTEXT_LAUNCHER = 3;
+
+    private Recognition() { }   // don't instantiate
+}
diff --git a/common/java/com/android/common/userhappiness/UserHappinessSignals.java b/common/java/com/android/common/userhappiness/UserHappinessSignals.java
new file mode 100644
index 0000000..347bdaa
--- /dev/null
+++ b/common/java/com/android/common/userhappiness/UserHappinessSignals.java
@@ -0,0 +1,45 @@
+/*
+ * 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.common.userhappiness;
+
+import android.content.Intent;
+import android.content.Context;
+import com.android.common.speech.LoggingEvents;
+
+/**
+ * Metrics for User Happiness are recorded here. Each app can define when to
+ * call these User Happiness metrics.
+ */
+public class UserHappinessSignals {
+
+    /**
+     *  Log when a user "accepted" IME text. Each application can define what
+     *  it means to "accept" text. In the case of Gmail, pressing the "Send"
+     *  button indicates text acceptance. We broadcast this information to
+     *  VoiceSearch LoggingEvents and use it to aggregate VoiceIME Happiness Metrics
+     */
+    public static void userAcceptedImeText(Context context) {
+        // Create a Voice IME Logging intent.
+        Intent i = new Intent(LoggingEvents.ACTION_LOG_EVENT);
+        i.putExtra(LoggingEvents.EXTRA_APP_NAME, LoggingEvents.VoiceIme.APP_NAME);
+        i.putExtra(LoggingEvents.EXTRA_EVENT, LoggingEvents.VoiceIme.IME_TEXT_ACCEPTED);
+        i.putExtra(LoggingEvents.EXTRA_CALLING_APP_NAME, context.getPackageName());
+        i.putExtra(LoggingEvents.EXTRA_TIMESTAMP, System.currentTimeMillis());
+        context.sendBroadcast(i);
+    }
+
+}
diff --git a/common/tests/Android.mk b/common/tests/Android.mk
new file mode 100644
index 0000000..7425552
--- /dev/null
+++ b/common/tests/Android.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2009 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CERTIFICATE := platform
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := AndroidCommonTests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := android-common
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
diff --git a/common/tests/AndroidManifest.xml b/common/tests/AndroidManifest.xml
new file mode 100644
index 0000000..151ec20
--- /dev/null
+++ b/common/tests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.common.tests"
+        android:sharedUserId="com.android.uid.test">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!-- Run tests with "runtest common" -->
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="com.android.common.tests"
+            android:label="Android Common Library Tests" />
+
+</manifest>
diff --git a/common/tests/src/com/android/common/OperationSchedulerTest.java b/common/tests/src/com/android/common/OperationSchedulerTest.java
new file mode 100644
index 0000000..955508f
--- /dev/null
+++ b/common/tests/src/com/android/common/OperationSchedulerTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2009 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.common;
+
+import android.content.SharedPreferences;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class OperationSchedulerTest extends AndroidTestCase {
+    /**
+     * OperationScheduler subclass which uses an artificial time.
+     * Set {@link #timeMillis} to whatever value you like.
+     */
+    private class TimeTravelScheduler extends OperationScheduler {
+        static final long DEFAULT_TIME = 1250146800000L;  // 13-Aug-2009, 12:00:00 am
+        public long timeMillis = DEFAULT_TIME;
+
+        @Override
+        protected long currentTimeMillis() { return timeMillis; }
+        public TimeTravelScheduler() { super(getFreshStorage()); }
+    }
+
+    private SharedPreferences getFreshStorage() {
+        SharedPreferences sp = getContext().getSharedPreferences("OperationSchedulerTest", 0);
+        sp.edit().clear().commit();
+        return sp;
+    }
+
+    @MediumTest
+    public void testScheduler() throws Exception {
+        TimeTravelScheduler scheduler = new TimeTravelScheduler();
+        OperationScheduler.Options options = new OperationScheduler.Options();
+        assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options));
+        assertEquals(0, scheduler.getLastSuccessTimeMillis());
+        assertEquals(0, scheduler.getLastAttemptTimeMillis());
+
+        long beforeTrigger = scheduler.timeMillis;
+        scheduler.setTriggerTimeMillis(beforeTrigger + 1000000);
+        assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options));
+
+        // It will schedule for the later of the trigger and the moratorium...
+        scheduler.setMoratoriumTimeMillis(beforeTrigger + 500000);
+        assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options));
+        scheduler.setMoratoriumTimeMillis(beforeTrigger + 1500000);
+        assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options));
+
+        // Test enable/disable toggle
+        scheduler.setEnabledState(false);
+        assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options));
+        scheduler.setEnabledState(true);
+        assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options));
+
+        // Backoff interval after an error
+        long beforeError = (scheduler.timeMillis += 100);
+        scheduler.onTransientError();
+        assertEquals(0, scheduler.getLastSuccessTimeMillis());
+        assertEquals(beforeError, scheduler.getLastAttemptTimeMillis());
+        assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options));
+        options.backoffFixedMillis = 1000000;
+        options.backoffIncrementalMillis = 500000;
+        assertEquals(beforeError + 1500000, scheduler.getNextTimeMillis(options));
+
+        // Two errors: backoff interval increases
+        beforeError = (scheduler.timeMillis += 100);
+        scheduler.onTransientError();
+        assertEquals(beforeError, scheduler.getLastAttemptTimeMillis());
+        assertEquals(beforeError + 2000000, scheduler.getNextTimeMillis(options));
+
+        // Reset transient error: no backoff interval
+        scheduler.resetTransientError();
+        assertEquals(0, scheduler.getLastSuccessTimeMillis());
+        assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options));
+        assertEquals(beforeError, scheduler.getLastAttemptTimeMillis());
+
+        // Permanent error holds true even if transient errors are reset
+        // However, we remember that the transient error was reset...
+        scheduler.onPermanentError();
+        assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options));
+        scheduler.resetTransientError();
+        assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options));
+        scheduler.resetPermanentError();
+        assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options));
+
+        // Success resets the trigger
+        long beforeSuccess = (scheduler.timeMillis += 100);
+        scheduler.onSuccess();
+        assertEquals(beforeSuccess, scheduler.getLastAttemptTimeMillis());
+        assertEquals(beforeSuccess, scheduler.getLastSuccessTimeMillis());
+        assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options));
+
+        // The moratorium is not reset by success!
+        scheduler.setTriggerTimeMillis(0);
+        assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options));
+        scheduler.setMoratoriumTimeMillis(0);
+        assertEquals(beforeSuccess, scheduler.getNextTimeMillis(options));
+
+        // Periodic interval after success
+        options.periodicIntervalMillis = 250000;
+        scheduler.setTriggerTimeMillis(Long.MAX_VALUE);
+        assertEquals(beforeSuccess + 250000, scheduler.getNextTimeMillis(options));
+
+        // Trigger minimum is also since the last success
+        options.minTriggerMillis = 1000000;
+        assertEquals(beforeSuccess + 1000000, scheduler.getNextTimeMillis(options));
+    }
+
+    @SmallTest
+    public void testParseOptions() throws Exception {
+         OperationScheduler.Options options = new OperationScheduler.Options();
+         assertEquals(
+                 "OperationScheduler.Options[backoff=0.0+5.0 max=86400.0 min=0.0 period=3600.0]",
+                 OperationScheduler.parseOptions("3600", options).toString());
+
+         assertEquals(
+                 "OperationScheduler.Options[backoff=0.0+2.5 max=86400.0 min=0.0 period=3700.0]",
+                 OperationScheduler.parseOptions("backoff=+2.5 3700", options).toString());
+
+         assertEquals(
+                 "OperationScheduler.Options[backoff=10.0+2.5 max=12345.6 min=7.0 period=3800.0]",
+                 OperationScheduler.parseOptions("max=12345.6 min=7 backoff=10 period=3800",
+                         options).toString());
+
+         assertEquals(
+                "OperationScheduler.Options[backoff=10.0+2.5 max=12345.6 min=7.0 period=3800.0]",
+                 OperationScheduler.parseOptions("", options).toString());
+    }
+
+    @SmallTest
+    public void testMoratoriumWithHttpDate() throws Exception {
+        TimeTravelScheduler scheduler = new TimeTravelScheduler();
+        OperationScheduler.Options options = new OperationScheduler.Options();
+
+        long beforeTrigger = scheduler.timeMillis;
+        scheduler.setTriggerTimeMillis(beforeTrigger + 1000000);
+        assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options));
+
+        scheduler.setMoratoriumTimeMillis(beforeTrigger + 2000000);
+        assertEquals(beforeTrigger + 2000000, scheduler.getNextTimeMillis(options));
+
+        long beforeMoratorium = scheduler.timeMillis;
+        assertTrue(scheduler.setMoratoriumTimeHttp("3000"));
+        long afterMoratorium = scheduler.timeMillis;
+        assertTrue(beforeMoratorium + 3000000 <= scheduler.getNextTimeMillis(options));
+        assertTrue(afterMoratorium + 3000000 >= scheduler.getNextTimeMillis(options));
+
+        options.maxMoratoriumMillis = Long.MAX_VALUE / 2;
+        assertTrue(scheduler.setMoratoriumTimeHttp("Fri, 31 Dec 2030 23:59:59 GMT"));
+        assertEquals(1924991999000L, scheduler.getNextTimeMillis(options));
+
+        assertFalse(scheduler.setMoratoriumTimeHttp("not actually a date"));
+    }
+
+    @SmallTest
+    public void testClockRollbackScenario() throws Exception {
+        TimeTravelScheduler scheduler = new TimeTravelScheduler();
+        OperationScheduler.Options options = new OperationScheduler.Options();
+        options.minTriggerMillis = 2000;
+
+        // First, set up a scheduler with reasons to wait: a transient
+        // error with backoff and a moratorium for a few minutes.
+
+        long beforeTrigger = scheduler.timeMillis;
+        long triggerTime = beforeTrigger - 10000000;
+        scheduler.setTriggerTimeMillis(triggerTime);
+        assertEquals(triggerTime, scheduler.getNextTimeMillis(options));
+        assertEquals(0, scheduler.getLastAttemptTimeMillis());
+
+        long beforeSuccess = (scheduler.timeMillis += 100);
+        scheduler.onSuccess();
+        scheduler.setTriggerTimeMillis(triggerTime);
+        assertEquals(beforeSuccess, scheduler.getLastAttemptTimeMillis());
+        assertEquals(beforeSuccess + 2000, scheduler.getNextTimeMillis(options));
+
+        long beforeError = (scheduler.timeMillis += 100);
+        scheduler.onTransientError();
+        assertEquals(beforeError, scheduler.getLastAttemptTimeMillis());
+        assertEquals(beforeError + 5000, scheduler.getNextTimeMillis(options));
+
+        long beforeMoratorium = (scheduler.timeMillis += 100);
+        scheduler.setMoratoriumTimeMillis(beforeTrigger + 1000000);
+        assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options));
+
+        // Now set the time back a few seconds.
+        // The moratorium time should still be honored.
+        long beforeRollback = (scheduler.timeMillis = beforeTrigger - 10000);
+        assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options));
+
+        // The rollback also moved the last-attempt clock back to the rollback time.
+        assertEquals(scheduler.timeMillis, scheduler.getLastAttemptTimeMillis());
+
+        // But if we set the time back more than a day, the moratorium
+        // resets to the maximum moratorium (a day, by default), exposing
+        // the original trigger time.
+        beforeRollback = (scheduler.timeMillis = beforeTrigger - 100000000);
+        assertEquals(triggerTime, scheduler.getNextTimeMillis(options));
+        assertEquals(beforeRollback, scheduler.getLastAttemptTimeMillis());
+
+        // If we roll forward until after the re-set moratorium, then it expires.
+        scheduler.timeMillis = triggerTime + 5000000;
+        assertEquals(triggerTime, scheduler.getNextTimeMillis(options));
+        assertEquals(beforeRollback, scheduler.getLastAttemptTimeMillis());
+        assertEquals(beforeRollback, scheduler.getLastSuccessTimeMillis());
+    }
+}
diff --git a/common/tools/make-iana-tld-pattern.py b/common/tools/make-iana-tld-pattern.py
new file mode 100755
index 0000000..de81c58
--- /dev/null
+++ b/common/tools/make-iana-tld-pattern.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+
+from urllib2 import urlopen
+
+TLD_PREFIX = r"""
+    /**
+     *  Regular expression to match all IANA top-level domains.
+     *  List accurate as of 2010/02/05.  List taken from:
+     *  http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+     *  This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py
+     */
+    public static final String TOP_LEVEL_DOMAIN_STR =
+"""
+TLD_SUFFIX = '";'
+
+URL_PREFIX = r"""
+    /**
+     *  Regular expression to match all IANA top-level domains for WEB_URL.
+     *  List accurate as of 2010/02/05.  List taken from:
+     *  http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+     *  This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py
+     */
+    public static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL =
+        "(?:"
+"""
+
+URL_SUFFIX = ';'
+
+class Bucket:
+    def __init__(self, baseLetter):
+        self.base=baseLetter
+        self.words=[]
+        self.letters=[]
+
+    def dump(self, isWebUrl=False, isFirst=False, isLast=False):
+        if (len(self.words) == 0) and (len(self.letters) == 0):
+            return ''
+
+        self.words.sort()
+        self.letters.sort()
+
+        output = '        ';
+
+        if isFirst:
+            if isWebUrl:
+                output += '+ "'
+            else:
+                output += '"('
+        else:
+            output += '+ "|'
+
+        if len(self.words) != 0:
+            output += '('
+
+            if isWebUrl:
+                output += '?:'
+
+        firstWord = 1
+        for word in self.words:
+            if firstWord == 0:
+                output += '|'
+            firstWord = 0
+            for letter in word:
+                if letter == '-':
+                    output += '\\\\'  # escape the '-' character.
+                output += letter
+
+        if len(self.words) > 0 and len(self.letters) > 0:
+            output += '|'
+
+        if len(self.letters) == 1:
+            output += '%c%c' % (self.base, self.letters[0])
+        elif len(self.letters) > 0:
+            output += '%c[' % self.base
+
+            for letter in self.letters:
+                output += letter
+
+            output += ']'
+
+        if len(self.words) != 0:
+            output += ')'
+
+        if not isLast:
+            output += '"'
+            output += '\n'
+
+        return output;
+
+    def add(self, line):
+        length = len(line)
+
+        if line.startswith('#') or (length == 0):
+            return;
+
+        if length == 2:
+            self.letters.append(line[1:2])
+        else:
+            self.words.append(line)
+
+def getBucket(buckets, line):
+    letter = line[0]
+    bucket = buckets.get(letter)
+
+    if bucket is None:
+        bucket = Bucket(letter)
+        buckets[letter] = bucket
+
+    return bucket
+
+def makePattern(prefix, suffix, buckets, isWebUrl=False):
+    output = prefix
+
+    output += getBucket(buckets, 'a').dump(isFirst=True, isWebUrl=isWebUrl)
+
+    for letter in range(ord('b'), ord('z')):
+        output += getBucket(buckets, chr(letter)).dump(isWebUrl=isWebUrl)
+
+    output += getBucket(buckets, 'z').dump(isLast=True, isWebUrl=isWebUrl)
+
+    if isWebUrl:
+        output += '))"'
+    else:
+        output += ')'
+
+    output += suffix
+
+    print output
+
+if __name__ == "__main__":
+    f = urlopen('http://data.iana.org/TLD/tlds-alpha-by-domain.txt')
+    domains = f.readlines()
+    f.close()
+
+    buckets = {}
+
+    for domain in domains:
+        domain = domain.lower()
+
+        if len(domain) > 0:
+            getBucket(buckets, domain[0]).add(domain.strip())
+
+    makePattern(TLD_PREFIX, TLD_SUFFIX, buckets, isWebUrl=False)
+    makePattern(URL_PREFIX, URL_SUFFIX, buckets, isWebUrl=True)